This R script is used to validate the points in the ReSurvey database using RS indicators (indices + phenology + canopy height).

Load libraries

library(tidyverse)
library(here)
library(gridExtra)
library(readxl)
library(scales)
library(sf)
library(rnaturalearth)
library(dtplyr)
library(lme4)
library(lmerTest)
library(car)
library(ggeffects)
library(party)
library(partykit)
library(strucchange)
library(ggparty)
library(caret)
library(moreparty)
library(randomForest)
library(pROC)
library(corrplot)

Define printall function

printall <- function(tibble) {
  print(tibble, width = Inf)
  }

Load geom_flat_violin plot

source("https://gist.githubusercontent.com/benmarwick/2a1bb0133ff568cbe28d/raw/fb53bd97121f7f9ce947837ef1a4c65a73bffb3f/geom_flat_violin.R")

Read data

data_validation<-read_tsv(here("data", "clean","final_RS_data_bands_S2.csv"))
Rows: 16353 Columns: 57
── Column specification ─────────────────────────────────────────────────────────────
Delimiter: "\t"
chr  (5): EUNISa_1, EUNISa_1_descr, biogeo, unit, Location method
dbl (52): PlotObservationID, EVI_max, NDVI_max, SAVI_max, EVI_min, NDVI_min, SAVI...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

No parsing issues!

Some data managenemt

TO-DO: Missing data checks

Do when all RS data is ready!

Distributions all bioregions

Indices

# Define a function to create histograms
plot_histogram <- function(data, x_var, x_label) {
  ggplot(data %>%
           dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
         aes(x = !!sym(x_var))) +
    geom_histogram(color = "black", fill = "white") +
    labs(x = x_label, y = "Frequency") +
    theme_bw()
}
# Define a function to create plots with violin + boxplot + points
distr_plot <- function(data, y_vars, y_labels) {
  for (i in seq_along(y_vars)) {
    y_var <- y_vars[[i]]
    y_label <- y_labels[[i]]
    
    p <- ggplot(data = data %>%
                  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
                aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
      geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
      geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
                 position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
      geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
      stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
      stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
                                                     label = length(x)),
                   geom = "text", aes(label = ..label..), vjust = 0.5) +
      labs(y = y_label, x = "EUNIS level 1") +
      scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
      guides(fill = FALSE, color = FALSE) +
      theme_bw() + coord_flip()
    
    print(p)
  }
}

Ranges of min and max:

range(data_validation$NDVI_max, na.rm = T) # NDVI_max > 1 (slightly)
[1] 0.06446001 1.09902698
range(data_validation$NDMI_max, na.rm = T) # NDMI_max > 1 (slightly)
[1] -0.1994688  1.0924980
range(data_validation$NDWI_max, na.rm = T)
[1] -0.7283135  0.6050488
range(data_validation$SAVI_max, na.rm = T)
[1] 0.01849304 0.85933671
range(data_validation$EVI_max, na.rm = T) # EVI_max > 1 (slightly)
[1] 0.05318326 1.14645898
range(data_validation$NDVI_min, na.rm = T)
[1] -0.6891069  0.8422586
range(data_validation$NDMI_min, na.rm = T)
[1] -0.6620149  0.5395376
range(data_validation$NDWI_min, na.rm = T) # NDWI_min < -1 (slightly)
[1] -1.0579643 -0.0262825
range(data_validation$SAVI_min, na.rm = T)
[1] -0.4959691  0.6207103
range(data_validation$EVI_min, na.rm = T) # EVI_min < -1!
[1] -4.1368512  0.6563168
nrow(data_validation %>% dplyr::filter(NDVI_max > 1))
[1] 12
nrow(data_validation %>% dplyr::filter(NDMI_max > 1))
[1] 70
nrow(data_validation %>% dplyr::filter(EVI_max > 1))
[1] 5
nrow(data_validation %>% dplyr::filter(NDWI_min < -1))
[1] 11
nrow(data_validation %>% dplyr::filter(EVI_min < -1))
[1] 15

Histograms to check that max and min values are ok:

plot_histogram(data_validation, "NDVI_max", "NDVI max")

plot_histogram(data_validation, "NDMI_max", "NDMI max")

plot_histogram(data_validation, "NDWI_max", "NDWI max")

plot_histogram(data_validation, "SAVI_max", "SAVI max")

plot_histogram(data_validation, "EVI_max", "EVI max")

plot_histogram(data_validation, "NDVI_min", "NDVI min")

plot_histogram(data_validation, "NDMI_min", "NDMI min")

plot_histogram(data_validation, "NDWI_min", "NDWI min")

plot_histogram(data_validation, "SAVI_min", "SAVI min")

plot_histogram(data_validation %>%
                 dplyr::filter(EVI_min > -1.5), "EVI_min", "EVI min")

nrow(data_validation %>%
       dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
       dplyr::filter(EVI_max > 1 | EVI_max < -1))
[1] 5
data_validation %>%
       dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q"))%>%
  dplyr::filter(EVI_max > 1 | EVI_max < -1) %>%
  count(biogeo, unit)

Most EVI values are ok!

Distribution plots:

distr_plot(data_validation %>% dplyr::filter(EVI_min > -0.5),
           c("NDVI_max", "EVI_max", "SAVI_max", "NDMI_max", "NDWI_max",
             "NDVI_min", "EVI_min", "SAVI_min", "NDMI_min", "NDWI_min"),
           c("NDVI_max", "EVI_max", "SAVI_max", "NDMI_max", "NDWI_max",
             "NDVI_min", "EVI_min", "SAVI_min", "NDMI_min", "NDWI_min"))

CH

distr_plot(data_validation, "canopy_height", "Canopy height (m)")

Show habitats with CH categories

ggplot(data_validation %>%
         # Keep only forests, grasslands, shrublands and wetlands
         dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
         mutate(CH_cat =
                  factor(
                    case_when(canopy_height == 0 ~ "0 m",
                              canopy_height > 0 & canopy_height <= 1 ~ "0-1 m",
                              canopy_height > 1 & canopy_height <=2 ~ "1-2 m",
                              canopy_height > 2 & canopy_height <=5 ~ "2-5 m",
                              canopy_height > 5 & canopy_height <=8 ~ "5-8 m",
                              canopy_height > 8 ~ "> 8 m",
                              is.na(canopy_height) ~ NA_character_),
                    levels = c(
                      "0 m", "0-1 m", "1-2 m", "2-5 m", "5-8 m", "> 8 m"))),
       aes(x = EUNISa_1_descr, fill = CH_cat)) +
  geom_bar() + theme_bw() + coord_flip() +
  scale_y_continuous(labels = label_number()) +
  scale_fill_viridis_d(direction = -1) +
  labs(x = "EUNIS level 1", fill = "Canopy height") +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  theme(legend.position = c(0.8, 0.75),
        legend.direction = "vertical")

Stats per habitat type

data_validation %>%
  # Keep only forests, grasslands, shrublands and wetlands
  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  group_by(EUNISa_1_descr) %>%
  summarise(across(canopy_height, list(
    mean = mean,
    median = median,
    sd = sd,
    min = min,
    max = max
    ), na.rm = TRUE))

Phenology

Only using NDVI- and SAVI-based values so far.

Maximum NDVI should be equal to value at peak?

nrow(data_validation %>% dplyr::filter(NDVI_pos_value  != NDVI_max))
[1] 403

Not sure why this happens, but in most cases the difference is small (< -0.1). Anyway, we should use only one of these two in the RF models.

plot_histogram(data_validation, "NDVI_sos_doy", "NDVI_sos_doy")

plot_histogram(data_validation, "NDVI_pos_doy", "NDVI_pos_doy")

plot_histogram(data_validation, "NDVI_eos_doy", "NDVI_eos_doy")

plot_histogram(data_validation, "EVI_sos_doy", "EVI_sos_doy")

plot_histogram(data_validation, "EVI_pos_doy", "EVI_pos_doy")

plot_histogram(data_validation, "EVI_eos_doy", "EVI_eos_doy")

plot_histogram(data_validation, "SAVI_sos_doy", "SAVI_sos_doy")

plot_histogram(data_validation, "SAVI_pos_doy", "SAVI_pos_doy")

plot_histogram(data_validation, "SAVI_eos_doy", "SAVI_eos_doy")

plot_histogram(data_validation, "NDVI_sos_value", "NDVI_sos_value")

plot_histogram(data_validation, "NDVI_pos_value", "NDVI_pos_value")

plot_histogram(data_validation, "NDVI_eos_value", "NDVI_eos_value")

plot_histogram(data_validation, "EVI_sos_value", "EVI_sos_value")

plot_histogram(data_validation, "EVI_pos_value", "EVI_pos_value")

plot_histogram(data_validation, "EVI_eos_value", "EVI_eos_value")

plot_histogram(data_validation, "NDVI_gsd", "NDVI_gsd")

plot_histogram(data_validation, "EVI_gsd", "EVI_gsd")

plot_histogram(data_validation, "SAVI_gsd", "SAVI_gsd")

plot_histogram(data_validation, "NDVI_diff_pos_sos_value",
               "NDVI_diff_pos_sos_value")

plot_histogram(data_validation, "EVI_diff_pos_sos_value",
               "EVI_diff_pos_sos_value")

plot_histogram(data_validation, "SAVI_diff_pos_sos_value",
               "SAVI_diff_pos_sos_value")

plot_histogram(data_validation, "NDVI_diff_pos_eos_value",
               "NDVI_diff_pos_eos_value")

plot_histogram(data_validation, "EVI_diff_pos_eos_value",
               "EVI_diff_pos_eos_value")

plot_histogram(data_validation, "SAVI_diff_pos_eos_value",
               "SAVI_diff_pos_eos_value")

plot_histogram(data_validation, "NDVI_diff_pos_sos_doy",
               "NDVI_diff_pos_sos_doy")

plot_histogram(data_validation, "EVI_diff_pos_sos_doy",
               "EVI_diff_pos_sos_doy")

plot_histogram(data_validation, "SAVI_diff_pos_sos_doy",
               "SAVI_diff_pos_sos_doy")

plot_histogram(data_validation, "NDVI_diff_eos_pos_doy",
               "NDVI_diff_eos_pos_doy")

plot_histogram(data_validation, "EVI_diff_eos_pos_doy", 
               "EVI_diff_eos_pos_doy")

plot_histogram(data_validation, "SAVI_diff_eos_pos_doy", 
               "SAVI_diff_eos_pos_doy")

plot_histogram(data_validation, "NDVI_auc", "NDVI_auc")

plot_histogram(data_validation, "EVI_auc", "EVI_auc")

plot_histogram(data_validation, "SAVI_auc", "SAVI_auc")

distr_plot(data_validation,
           c("NDVI_sos_value","NDVI_pos_value", "NDVI_eos_value",
             "EVI_sos_value","EVI_pos_value", "EVI_eos_value",
             "SAVI_sos_value", "SAVI_pos_value", "SAVI_eos_value",
             "NDVI_sos_doy","NDVI_pos_doy", "NDVI_eos_doy",
             "EVI_sos_doy","EVI_pos_doy", "EVI_eos_doy",
             "SAVI_sos_doy", "SAVI_pos_doy", "SAVI_eos_doy"),
           c("NDVI_sos_value","NDVI_pos_value", "NDVI_eos_value",
             "EVI_sos_value","EVI_pos_value", "EVI_eos_value",
             "SAVI_sos_Value", "SAVI_pos_value", "SAVI_eos_value",
             "NDVI_sos_doy","NDVI_pos_doy", "NDVI_eos_doy",
             "EVI_sos_doy","EVI_pos_doy", "EVI_eos_doy",
             "SAVI_sos_doy", "SAVI_pos_doy", "SAVI_eos_doy")
           )

distr_plot(data_validation,
           c("NDVI_gsd","EVI_gsd", "SAVI_gsd",
             "NDVI_diff_pos_sos_value", "EVI_diff_pos_sos_value",
             "SAVI_diff_pos_sos_value",
             "NDVI_diff_pos_eos_value", "EVI_diff_pos_eos_value",
             "SAVI_diff_pos_eos_value",
             "NDVI_diff_pos_sos_doy", "EVI_diff_pos_sos_doy",
             "SAVI_diff_pos_sos_doy",
             "NDVI_diff_eos_pos_doy", "EVI_diff_eos_pos_doy",
             "SAVI_diff_eos_pos_doy"),
           c("NDVI_gsd","EVI_gsd", "SAVI_gsd",
             "NDVI_diff_pos_sos_value", "EVI_diff_pos_sos_value",
             "SAVI_diff_pos_sos_Value",
             "NDVI_diff_pos_eos_value", "EVI_diff_pos_eos_value",
             "SAVI_diff_pos_eos_value",
             "NDVI_diff_pos_sos_doy", "EVI_diff_pos_sos_doy",
             "SAVI_diff_pos_sos_doy",
             "NDVI_diff_eos_pos_doy", "EVI_diff_eos_pos_doy",
             "SAVI_diff_eos_pos_doy")
           )

distr_plot(data_validation,
           c("NDVI_auc", "EVI_auc", "SAVI_auc"),
             c("NDVI_auc", "EVI_auc", "SAVI_auc"))

TBD: Distributions per bioregion

# Define a function to create plots with violin + boxplot + points
distr_plot_biogeo <- function(data, y_vars, y_labels) {
  plots <- list()
  
  for (i in seq_along(y_vars)) {
    y_var <- y_vars[[i]]
    y_label <- y_labels[[i]]
    
    p <- ggplot(data = data %>%
                  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
                aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
      geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
      geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
                 position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
      geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
      stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
      stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
                                                     label = length(x)),
                   geom = "text", aes(label = ..label..), vjust = 0.5) +
      labs(y = y_label, x = "EUNISa_1_descr") +
      scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
      guides(fill = FALSE, color = FALSE) +
      theme_bw() + coord_flip() + facet_wrap(~ biogeo)
    
    plots[[y_var]] <- p
  }
  
  return(plots)
}

Indices

Distribution plots:

CH

distr_plot_biogeo(data_validation, "canopy_height", "Canopy height (m)")
$canopy_height

Phenology

RF models

vars_RF <- c(
  # Min values of all indices
  "NDVI_min", "EVI_min", "NDMI_min", "NDWI_min", "SAVI_min",
  # Max values of NDMI and NDWI
  "NDMI_max", "NDWI_max",
  # AUC of NDVI, EVI and SAVI
  "NDVI_auc", "EVI_auc", "SAVI_auc",
  # Values of NDVI, EVI and SAVI at sos, pos and eos
  "NDVI_sos_value", "NDVI_pos_value", "NDVI_eos_value",
  "EVI_sos_value", "EVI_pos_value", "EVI_eos_value", 
  "SAVI_sos_value", "SAVI_pos_value", "SAVI_eos_value",
  # Differences pos-sos in value and doy
  "NDVI_diff_pos_sos_value", "EVI_diff_pos_sos_value", "SAVI_diff_pos_sos_value",
  "NDVI_diff_pos_sos_doy", "EVI_diff_pos_sos_doy", "SAVI_diff_pos_sos_doy",
  # Differences pos-eos in value and doy
  "NDVI_diff_pos_eos_value", "EVI_diff_pos_eos_value","SAVI_diff_pos_eos_value",
  "NDVI_diff_eos_pos_doy", "EVI_diff_eos_pos_doy", "SAVI_diff_eos_pos_doy",
  # Growing season duration
  "NDVI_gsd", "EVI_gsd", "SAVI_gsd",
  # Canopy height
  "canopy_height")

RF with all points

filtered_data0 <- data_validation %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  # Remove all rows with wrong values of indices (not between -1 and 1)
  dplyr::filter(EVI_max <= 1 & EVI_min >= -1) %>%
  dplyr::filter(NDVI_max <= 1) %>%
  dplyr::filter(NDMI_max <= 1) %>%
  dplyr::filter(NDWI_min >= -1) %>%
  # Remove rows with missing values
  dplyr::filter(if_all(all_of(vars_RF), ~ !is.na(.))) %>%
  # Keep only rows with differences > 0
  dplyr::filter(if_all(contains("diff"), ~ .x > 0)) %>%
  # Select only variables needed
  select(EUNISa_1, all_of(vars_RF))

Correlation of all variables to be included in RF models:

corrplot(filtered_data0 %>% 
           select(all_of(vars_RF)) %>%
           cor(use = "pairwise.complete.obs"),
         method = "color", type = "upper", tl.col = "black", tl.srt = 45)

Split into training and test data sets.

train_indices0 <- sample(1:nrow(filtered_data0), 0.7 * nrow(filtered_data0))
train_data0 <- filtered_data0[train_indices0, ]
test_data0 <- filtered_data0[-train_indices0, ]

Number of points per category for filtered data:

filtered_data0 %>% count(EUNISa_1)
rf_cforest0 <- party::cforest(
  formula = reformulate(vars_RF, response = "EUNISa_1"),
  data = train_data0, 
  controls = cforest_control(mtry = round(sqrt(length(all_of(vars_RF)))),  
                             # mtry = sqrt(35) # sqrt of total n variables
                             # Default mtry = 5
                             # Bagging: mtry = NULL
                             # or = number of input variables
                             ntree = 500) # Default, try increasing
  ) 
predictions_rf_cforest0 <- predict(rf_cforest0, newdata = test_data0,
                                   OOB = TRUE, type = "response")
beep()

Confusion matrix:

confusionMatrix(predictions_rf_cforest0, test_data0$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  113   17   10    1
         R  378 2114  320  112
         S   77   95  570   27
         T   26   64   33  608

Overall Statistics
                                         
               Accuracy : 0.7459         
                 95% CI : (0.733, 0.7585)
    No Information Rate : 0.5016         
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.5861         
                                         
 Mcnemar's Test P-Value : < 2.2e-16      

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity           0.19024   0.9231   0.6109   0.8128
Specificity           0.99295   0.6440   0.9452   0.9678
Pos Pred Value        0.80142   0.7230   0.7412   0.8317
Neg Pred Value        0.89127   0.8927   0.9044   0.9635
Prevalence            0.13012   0.5016   0.2044   0.1639
Detection Rate        0.02475   0.4631   0.1249   0.1332
Detection Prevalence  0.03089   0.6405   0.1685   0.1601
Balanced Accuracy     0.59159   0.7836   0.7781   0.8903
varimp_rf_cforest0 <- party::varimp(rf_cforest0, conditional = F) 

Variable Importance Plot

varimp_rf_cforest0_df <- data.frame(Variable = names(varimp_rf_cforest0),
                                    Importance = varimp_rf_cforest0)
ggplot(varimp_rf_cforest0_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest0, newdata = test_data0, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data0$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()
G3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
g
# Step 5: Plot ROC curves with ggplot2
roc0 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc0

RF with all GPS points (diff or not)

filtered_data1 <- data_validation %>%
  # Select only GPS points
  dplyr::filter(`Location method` == "Location with GPS" |
                  `Location method` == "Location with differential GPS") %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  # Remove all rows with wrong values of indices (not between -1 and 1)
  dplyr::filter(EVI_max <= 1 & EVI_min >= -1) %>%
  dplyr::filter(NDVI_max <= 1) %>%
  dplyr::filter(NDMI_max <= 1) %>%
  dplyr::filter(NDWI_min >= -1) %>%
  # Remove rows with missing values
  dplyr::filter(if_all(all_of(vars_RF), ~ !is.na(.))) %>%
  # Keep only rows with differences > 0
  dplyr::filter(if_all(contains("diff"), ~ .x > 0)) %>%
  # Select only variables needed
  select(EUNISa_1, all_of(vars_RF))

Split into training and test data sets.

train_indices1 <- sample(1:nrow(filtered_data1), 0.7 * nrow(filtered_data1))
train_data1 <- filtered_data1[train_indices1, ]
test_data1 <- filtered_data1[-train_indices1, ]

Number of points per category for filtered data:

filtered_data1 %>% count(EUNISa_1)
rf_cforest1 <- party::cforest(
  formula = reformulate(vars_RF, response = "EUNISa_1"),
  data = train_data1, 
  controls = cforest_control(mtry = round(sqrt(length(all_of(vars_RF)))),  
                             # mtry = sqrt(35) # sqrt of total n variables
                             # Default mtry = 5
                             # Bagging: mtry = NULL
                             # or = number of input variables
                             ntree = 500) # Default, try increasing
  ) 
beep()
predictions_rf_cforest1 <- predict(rf_cforest1, newdata = test_data1,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest1, test_data1$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  158   21   29    0
         R  238 1106  156   66
         S   96  107  522   13
         T   12   27   10  110

Overall Statistics
                                         
               Accuracy : 0.7098         
                 95% CI : (0.6922, 0.727)
    No Information Rate : 0.4721         
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.5395         
                                         
 Mcnemar's Test P-Value : < 2.2e-16      

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity           0.31349   0.8771   0.7280  0.58201
Specificity           0.97693   0.6738   0.8895  0.98026
Pos Pred Value        0.75962   0.7063   0.7073  0.69182
Neg Pred Value        0.85952   0.8597   0.8991  0.96855
Prevalence            0.18869   0.4721   0.2684  0.07076
Detection Rate        0.05915   0.4141   0.1954  0.04118
Detection Prevalence  0.07787   0.5863   0.2763  0.05953
Balanced Accuracy     0.64521   0.7754   0.8087  0.78113
varimp_rf_cforest1 <- party::varimp(rf_cforest1, conditional = F) 

Variable Importance Plot

varimp_rf_cforest1_df <- data.frame(Variable = names(varimp_rf_cforest1),
                                    Importance = varimp_rf_cforest1)
ggplot(varimp_rf_cforest1_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()
G3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
g
# Step 5: Plot ROC curves with ggplot2
roc1 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc1

RF with …

OLD FROM HERE

First validation

For T, R, S, Q habitats.

Define a set of rules for a first validation of ALL ReSurvey data. We can call these “Expert-based” rules.

Number of observations in ReSurvey from the habitats of interest:

nrow(data_validation %>%
       dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")))

Number of observations in ReSurvey from the habitats of interest and with all RS data:

nrow(data_validation %>%
       dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
       dplyr::filter(CH_data == T) %>%
       dplyr::filter(RS_data ==T) %>%
       dplyr::filter(RS_phen_data == T))
data_validation_terrestrial <- data_validation %>%
  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q"))

Define rules

Create column for first validation based on different indicators, where “wrong” is noted when the validation rule is not met. Include EUNIS1 confusions.

data_validation_terrestrial %>% count(EUNISa_1, EUNIS1_conf_type)

Define rules:

data_validation_terrestrial <-
  data_validation_terrestrial %>%
  mutate(
    valid_1_NDWI = case_when(
      # Points that are basically water
      NDWI_max > 0.3 ~ "wrong",
      TRUE ~ NA_character_),
    valid_1_CH = case_when(
      # T points with low CH
      EUNISa_1 == "T" & canopy_height < 8 ~ "wrong",
      # S points with low CH
      EUNISa_1 =="S" & canopy_height < 5 ~ "wrong",
      # R & Q points with high CH
      EUNISa_1 %in% c("R", "Q") & canopy_height > 2 ~ "wrong",
      TRUE ~ NA_character_),
    valid_1_NDVI = case_when(
      # T points with low NDVI_max
      EUNISa_1 == "T" & NDVI_max < 0.6 ~ "wrong",
      # S-R-Q points with low NDVI_max
      EUNISa_1 %in% c("R", "S", "Q") & NDVI_max < 0.2 ~ "wrong",
      TRUE ~ NA_character_),
    # Count how many validation rules are not met
    valid_1_count = rowSums(across(c(valid_1_NDWI, valid_1_CH, valid_1_NDVI), 
                             ~ . == "wrong"), na.rm = TRUE),
    # Points where at least 1 rule not met
    valid_1 = if_else(valid_1_count > 0, "At least 1 rule broken",
                      "No rules broken so far")
    )

Plots first validation

ggplot(data_validation_terrestrial%>%
         mutate(rules_broken = case_when(
           valid_1_count == 1 & valid_1_NDWI == "wrong" ~ "NDWI",
           valid_1_count == 1 & valid_1_NDVI == "wrong" ~ "NDVI",
           valid_1_count == 1 & valid_1_CH == "wrong" ~ "CH",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_NDVI == "wrong"~ "NDWI + NDVI",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_CH == "wrong"~ "NDWI + CH",
           valid_1_count == 2 &
             valid_1_NDVI == "wrong" & valid_1_CH == "wrong"~ "NDVI + CH",
           valid_1_count == 3 ~ "NDWI + NDVI + CH",
           TRUE ~ NA_character_
         )), 
       aes(x = valid_1_count, fill = rules_broken)) +
  geom_bar() + labs(x = "Number of broken rules")
data_validation_terrestrial %>%
         mutate(rules_broken = case_when(
           valid_1_count == 1 & valid_1_NDWI == "wrong" ~ "NDWI",
           valid_1_count == 1 & valid_1_NDVI == "wrong" ~ "NDVI",
           valid_1_count == 1 & valid_1_CH == "wrong" ~ "CH",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_NDVI == "wrong"~ "NDWI + NDVI",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_CH == "wrong"~ "NDWI + CH",
           valid_1_count == 2 &
             valid_1_NDVI == "wrong" & valid_1_CH == "wrong"~ "NDVI + CH",
           valid_1_count == 3 ~ "NDWI + NDVI + CH",
           TRUE ~ NA_character_
         )) %>%
  count(rules_broken, EUNIS1_conf_type)

Proportion of observations not validated (so far):

nrow(data_validation_terrestrial %>% dplyr::filter(valid_1_count > 0))/
  nrow(data_validation_terrestrial)

But be aware that there are still MANY missing RS data.

ggplot(data_validation_terrestrial %>%
         mutate(diff_GPS = if_else(
           `Location method` != "Location with differential GPS" |
             is.na(`Location method`), "no", "yes")), 
       aes(x = diff_GPS, fill = valid_1)) +
  geom_bar() + labs(x = "Differential GPS")
ggplot(data_validation_terrestrial %>%
         mutate(GPS = case_when(
           `Location method` == "Location with differential GPS" ~ "yes",
           `Location method` == "Location with GPS" ~ "yes",
           is.na(`Location method`) ~ "no",
           TRUE ~ "no"
         )), 
       aes(x = GPS, fill = valid_1)) +
  geom_bar() + labs(x = "GPS")

Points with any rule broken and confusion between EUNIS:

nrow(data_validation_terrestrial %>%
       dplyr::filter(EUNIS1_conf == T & valid_1_count > 0))

Convert to shp to look at these in GIS:

# st_write(data_validation_terrestrial %>%
#            dplyr::filter(EUNIS1_conf == T & valid_1_count > 0) %>%
#            st_as_sf(coords = c("Lon_updated", "Lat_updated"), crs = 4326),
#          "C:/GIS/MOTIVATE/shapefiles/resurv_not_val_EUNIS_conf.shp")

Checked and yes

How many points with differential GPS that have at least 1 rule broken?

nrow(data_validation_terrestrial %>%
  dplyr::filter(`Location method` == "Location with differential GPS" &
           valid_1 == "At least 1 rule broken"))

Convert to shp to look at these in GIS:

# st_write(data_validation_terrestrial %>%
#            dplyr::filter(`Location method` == "Location with differential GPS" &
#                     valid_1 == "At least 1 rule broken") %>%
#            st_as_sf(coords = c("Lon_updated", "Lat_updated"), crs = 4326),
#          "C:/GIS/MOTIVATE/shapefiles/resurv_not_val_diff_GPS.shp")

Maps

Points GPS

# Load world boundaries
world <- ne_countries(scale = "medium", returnclass = "sf")

# Calculate the extent of the points
points_GPS_extent <- data_validation %>%
  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  dplyr::filter(S2_data == T | Landsat_data == T ) %>%
  dplyr::filter(`Location method` == "Location with differential GPS" |
           `Location method` == "Location with GPS") %>%
  summarise(lon_min = min(Lon_updated, na.rm = TRUE),
            lon_max = max(Lon_updated, na.rm = TRUE),
            lat_min = min(Lat_updated, na.rm = TRUE),
            lat_max = max(Lat_updated, na.rm = TRUE))

# Add padding to the extent (adjust as needed)
padding <- 2  # Adjust padding to your preference
x_limits <- c(points_GPS_extent$lon_min - padding,
              points_GPS_extent$lon_max + padding)
y_limits <- c(points_GPS_extent$lat_min - padding,
              points_GPS_extent$lat_max + padding)

# Create the zoomed map
ggplot() +
  geom_sf(data = world, fill = "lightblue", color = "gray") +
  geom_point(data = data_validation %>%
               dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
               dplyr::filter(S2_data == T | Landsat_data == T ) %>%
               dplyr::filter(`Location method` == "Location with differential GPS" |
           `Location method` == "Location with GPS"),
             aes(x = Lon_updated, y = Lat_updated, color = EUNISa_1),
             size = 1) +
  coord_sf(xlim = x_limits, ylim = y_limits) +
  theme_minimal()

Number of GPS points by Country:

data_validation %>%
  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  dplyr::filter(S2_data == T | Landsat_data == T ) %>%
  dplyr::filter(`Location method` == "Location with differential GPS" |
           `Location method` == "Location with GPS") %>%
  count(Country)

Points ReSurvey

# Calculate the extent of the points
points_resurvey_extent <- data_validation %>%
  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  dplyr::filter(S2_data == T | Landsat_data == T ) %>%
  summarise(lon_min = min(Lon_updated, na.rm = TRUE),
            lon_max = max(Lon_updated, na.rm = TRUE),
            lat_min = min(Lat_updated, na.rm = TRUE),
            lat_max = max(Lat_updated, na.rm = TRUE))

# Add padding to the extent (adjust as needed)
padding <- 2  # Adjust padding to your preference
x_limits <- c(points_resurvey_extent$lon_min - padding,
              points_resurvey_extent$lon_max + padding)
y_limits <- c(points_resurvey_extent$lat_min - padding,
              points_resurvey_extent$lat_max + padding)

# Create the zoomed map
ggplot() +
  geom_sf(data = world, fill = "lightblue", color = "gray") +
  geom_point(data = data_validation %>%
               dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
               dplyr::filter(S2_data == T | Landsat_data == T ),
             aes(x = Lon_updated, y = Lat_updated, color = EUNISa_1),
             size = 1) +
  coord_sf(xlim = x_limits, ylim = y_limits) +
  theme_minimal()

Number of ReSurvey points by Country:

data_validation %>%
  dplyr::filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  dplyr::filter(S2_data == T | Landsat_data == T ) %>%
  count(Country)

Distributions from GPS points without rules broken so far

Create tibble with differential GPS points without rules broken so far:

all_GPS_valid <- data_validation_terrestrial %>%
  dplyr::filter((`Location method` == "Location with differential GPS" | 
            `Location method` == "Location with GPS" ) &
           valid_1 == "No rules broken so far") 

NDVI, NDMI, NDWI, SAVI and EVI

distr_plot(all_GPS_valid,
           c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"), 
           c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))
distr_plot(all_GPS_valid,
           c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"), 
           c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))
distr_plot(all_GPS_valid,
           c("NDWI_max", "NDWI_p90", "NDWI_min", "NDWI_p10"), 
           c("NDWI max", "NDWI p90", "NDWI min", "NDWI p10"))
distr_plot(all_GPS_valid,
           c("SAVI_max", "SAVI_p90", "SAVI_min", "SAVI_p10"), 
           c("SAVI max", "SAVI p90", "SAVI min", "SAVI p10"))
distr_plot(all_GPS_valid %>%
             dplyr::filter(EVI_max <= 1) %>%
             dplyr::filter(EVI_min >= -1 & EVI_min <= 1),
           c("EVI_max", "EVI_p90", "EVI_min", "EVI_p10"), 
           c("EVI max", "EVI p90", "EVI min", "EVI p10"))

CH

distr_plot(all_GPS_valid, "canopy_height", "Canopy height (m)")

Phenology

distr_plot(all_GPS_valid,
           c("SOS_DOY","Peak_DOY", "EOS_DOY",
             "NDVI_at_SOS", "NDVI_at_Peak", "NDVI_at_EOS",
             "diff_Peak_SOS","diff_Peak_EOS", "Season_Length"),
           c("SOS DOY", "Peak DOY", "EOS DOY",
             "NDVI at SOS", "NDVI at Peak", "NDVI at EOS",
             "Difference Peak-SOS", "Difference Peak-EOS", "Season Length"))

GPS valid points above p20 of NDVI_max and NDMI_min for each habitat

HERE!

Chosen NDVI_min because it was important in RF models, but let’s see with new data!

percentiles_all_GPS <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(percentile_20_NDVI_max = quantile(NDVI_max, probs = 0.20, na.rm = T),
            percentile_20_NDMI_min = quantile(NDMI_min, probs = 0.20, na.rm = T))

all_GPS_valid <- all_GPS_valid %>%
  left_join(percentiles_all_GPS, by = "EUNISa_1") %>%
  mutate(category_NDVI_max = case_when(
    NDVI_max < percentile_20_NDVI_max ~ "below_20th",
    NDVI_max >= percentile_20_NDVI_max ~ "above_20th"),
  category_NDMI_min = case_when(
    NDMI_min < percentile_20_NDMI_min ~ "below_20th",
    NDMI_min >= percentile_20_NDMI_min ~ "above_20th"))

ggplot(data = all_GPS_valid,
       aes(x = EUNISa_1_descr, y = NDVI_max)) +
  geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8,
                   fill = "lightblue") +
  geom_point(aes(color = category_NDVI_max),
             position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
  geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
  stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
  stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1, label = length(x)),
               geom = "text", aes(label = ..label..), vjust = 0.5) +
  labs(y = "NDVI max", x = "EUNIS level 1") +
  guides(fill = FALSE, color = FALSE) +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  scale_color_manual(values = c("below_20th" = "grey", "above_20th" = "lightblue")) +
  theme_bw() + coord_flip()

ggplot(data = all_GPS_valid,
       aes(x = EUNISa_1_descr, y = NDMI_min)) +
  geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8,
                   fill = "lightblue") +
  geom_point(aes(color = category_NDMI_min),
             position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
  geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
  stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
  stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1, label = length(x)),
               geom = "text", aes(label = ..label..), vjust = 0.5) +
  labs(y = "NDMI min", x = "EUNIS level 1") +
  guides(fill = FALSE, color = FALSE) +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  scale_color_manual(values = c("below_20th" = "grey", "above_20th" = "lightblue")) +
  theme_bw() + coord_flip()

RF models

Using the conditional inference version of random forest (cforest in package party). Suggested if the data are highly correlated. Cforest is more stable in deriving variable importance values in the presence of highly correlated variables, thus providing better accuracy in calculating variable importance (ref below).

Hothorn, T., Hornik, K. and Zeileis, A. (2006) Unbiased Recursive Portioning: A Conditional Inference Framework. Journal of Computational and Graphical Statistics, 15, 651- 674. http://dx.doi.org/10.1198/106186006X133933

All GPS points

dplyr::filtered_data0 <- all_GPS_valid %>%
  dplyr::filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  dplyr::filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices0 <- sample(1:nrow(dplyr::filtered_data0), 0.7 * nrow(dplyr::filtered_data0))
train_data0 <- dplyr::filtered_data0[train_indices0, ]
test_data0 <- dplyr::filtered_data0[-train_indices0, ]

Number of points per category for dplyr::filtered data:

dplyr::filtered_data0 %>% count(EUNISa_1)
rf_cforest0 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data0,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest0 <- predict(rf_cforest0, newdata = test_data0,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest0, test_data0$EUNISa_1)
varimp_rf_cforest0 <- party::varimp(rf_cforest0, conditional = F) 

Variable Importance Plot

varimp_rf_cforest0_df <- data.frame(Variable = names(varimp_rf_cforest0),
                                    Importance = varimp_rf_cforest0)
ggplot(varimp_rf_cforest0_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest0, newdata = test_data0, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data0$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()

# Step 5: Plot ROC curves with ggplot2
roc0 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc0

REVISE FROM HERE: All GPS points above p20

dplyr::filter the data to get only GPS-points above p20 of NDVI_max and NDMI_min.

all_GPS_valid <- all_GPS_valid %>%
  select(-percentile_20_NDVI_max, -percentile_20_NDMI_min)
percentiles <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(
    percentile_20_NDVI_max = quantile(NDVI_max, 0.20, na.rm = T),
    percentile_20_NDMI_min = quantile(NDMI_min, 0.20, na.rm = T),
    percentile_80_NDVI_max = quantile(NDVI_max, 0.80, na.rm = T),
    percentile_80_NDMI_min = quantile(NDMI_min, 0.80, na.rm = T)
    )

# Join the percentiles back to the original data
all_GPS_valid <- all_GPS_valid %>%
  left_join(percentiles, by = "EUNISa_1")

# dplyr::filter rows above the 20th percentile for both variables for each category of EUNISa_1
dplyr::filtered_data1 <- all_GPS_valid %>%
  dplyr::filter(
    NDVI_max >= percentile_20_NDVI_max & NDMI_min >= percentile_20_NDMI_min
    ) %>%
  dplyr::filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  dplyr::filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices1 <- sample(1:nrow(dplyr::filtered_data1), 0.7 * nrow(dplyr::filtered_data1))
train_data1 <- dplyr::filtered_data1[train_indices1, ]
test_data1 <- dplyr::filtered_data1[-train_indices1, ]

Number of points per category for dplyr::filtered data:

dplyr::filtered_data1 %>% count(EUNISa_1)

Investigate package ggparty (e.g. autoplot function, and more).

TO-DO: Choose the hyperparameter mtry based on the square root of the number of predictor variables (Hastie et al., 2009)-

Hastie, T., Tibshirani, R., & Friedman, J. (2009). The elements of statistical learning: Data mining, inference, and prediction. Springer Science & Business Media.

Maybe TO_DO: We variated ntree from 50 to 800 in steps of 50, leaving mtry constant at 2. Tis parameter variation showed that ntree=500 was optimal, while higher ntree led to no further model improvement (Supplementary Fig. S10). Subsequently, the hyperparameter mtry was varied from 2 to 8 with constant ntree=500. Here, mtry=3 led to the best results in almost all cases (Supplementary Fig. S11). Consequently, we chose ntree=500 and mtry=3 for our main analysis across all study sites.

rf_cforest1 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data1,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest1 <- predict(rf_cforest1, newdata = test_data1,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest1, test_data1$EUNISa_1)

SurrogateTree –> does not work

varimp_rf_cforest1 <- party::varimp(rf_cforest1, conditional = F) 

Variable Importance Plot

varimp_rf_cforest1_df <- data.frame(Variable = names(varimp_rf_cforest1),
                                    Importance = varimp_rf_cforest1)
ggplot(varimp_rf_cforest1_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

Tree Visualization

# Create a single conditional inference tree using ctree
single_tree1 <- ctree(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max + NDMI_min +
                       NDWI_max + NDWI_min + EVI_max + EVI_min + SAVI_max +
                       SAVI_min + canopy_height,
                     data = train_data1)

# Plot the single tree using
autoplot(single_tree1)

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()

# Step 5: Plot ROC curves with ggplot2
roc1 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc1

All GPS points within IQ range

dplyr::filter the data to get only GPS-points within IQ range of NDVI_max and NDMI_min.

IQ_ranges <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(
    Q1_NDVI_max = quantile(NDVI_max, 0.25, na.rm = T),
    Q1_NDMI_min = quantile(NDMI_min, 0.25, na.rm = T),
    Q3_NDVI_max = quantile(NDVI_max, 0.75, na.rm = T),
    Q3_NDMI_min = quantile(NDMI_min, 0.75, na.rm = T),
    IQR_NDVI_max = IQR(NDVI_max, na.rm = TRUE),
    IQR_NDMI_min = IQR(NDMI_min, na.rm = TRUE)
    )

# Join the IQ ranges back to the original data
all_GPS_valid <- all_GPS_valid %>%
  left_join(IQ_ranges, by = "EUNISa_1")

# Filter rows within the IQR range for both variables
dplyr::filtered_data2 <- all_GPS_valid %>%
  dplyr::filter(
    (NDVI_max >= Q1_NDVI_max & NDVI_max <= Q3_NDVI_max) &
    (NDMI_min >= Q1_NDMI_min & NDMI_min <= Q3_NDMI_min)
    ) %>%
  dplyr::filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  dplyr::filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices2 <- sample(1:nrow(filtered_data2), 0.7 * nrow(filtered_data2))
train_data2 <- filtered_data2[train_indices2, ]
test_data2 <- filtered_data2[-train_indices2, ]

Number of points per category for filtered data:

filtered_data2 %>% count(EUNISa_1)
rf_cforest2 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data2,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest2 <- predict(rf_cforest2, newdata = test_data2,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest2, test_data2$EUNISa_1)
varimp_rf_cforest2 <- party::varimp(rf_cforest2, conditional = F) 

Variable Importance Plot

varimp_rf_cforest2_df <- data.frame(Variable = names(varimp_rf_cforest2),
                                    Importance = varimp_rf_cforest2)
ggplot(varimp_rf_cforest2_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()

# Step 5: Plot ROC curves with ggplot2
roc2 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc2

All GPS points within 1.5 * IQ range

Filter the data to get only GPS-points within 1.5 * IQ range of NDVI_max and NDMI_min.

# Filter rows within the 1.5 * IQR range for both variables
filtered_data3 <- all_GPS_valid %>%
  filter(
    (NDVI_max >= (Q1_NDVI_max - 1.5 * IQR_NDVI_max) & NDVI_max <= (Q3_NDVI_max + 1.5 * IQR_NDVI_max)) &
      (NDMI_min >= (Q1_NDMI_min - 1.5 * IQR_NDMI_min) & NDMI_min <= (Q3_NDMI_min + 1.5 * IQR_NDMI_min))
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices3 <- sample(1:nrow(filtered_data3), 0.7 * nrow(filtered_data3))
train_data3 <- filtered_data3[train_indices3, ]
test_data3 <- filtered_data3[-train_indices3, ]

Number of points per category for filtered data:

filtered_data3 %>% count(EUNISa_1)
rf_cforest3 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data3,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest3 <- predict(rf_cforest3, newdata = test_data3,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest3, test_data3$EUNISa_1)
varimp_rf_cforest3 <- party::varimp(rf_cforest3, conditional = F) 

Variable Importance Plot

varimp_rf_cforest3_df <- data.frame(Variable = names(varimp_rf_cforest3),
                                    Importance = varimp_rf_cforest3)
ggplot(varimp_rf_cforest3_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()

# Step 5: Plot ROC curves with ggplot2
roc3 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc3

All GPS points within mean +/- SD

Filter the data to get only GPS-points within mean +/- SD of NDVI_max and NDMI_min.

mean_sd <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(
    mean_NDVI_max = mean(all_GPS_valid$NDVI_max, na.rm = T),
    mean_NDMI_min = mean(all_GPS_valid$NDMI_min, na.rm = T),
    sd_NDVI_max = sd(all_GPS_valid$NDVI_max, na.rm = T),
    sd_NDMI_min = sd(all_GPS_valid$NDMI_min, na.rm = T)
    )

# Join the IQ ranges back to the original data
all_GPS_valid <- all_GPS_valid %>%
  left_join(mean_sd, by = "EUNISa_1")

# Filter rows within the specified range for both variables
filtered_data4 <- all_GPS_valid %>%
  filter(
    (NDVI_max >= (mean_NDVI_max - sd_NDVI_max) & NDVI_max <= (mean_NDVI_max + sd_NDVI_max)) &
      (NDMI_min >= (mean_NDMI_min - sd_NDMI_min) & NDMI_min <= (mean_NDMI_min + sd_NDMI_min))
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices4 <- sample(1:nrow(filtered_data4), 0.7 * nrow(filtered_data4))
train_data4 <- filtered_data4[train_indices4, ]
test_data4 <- filtered_data4[-train_indices4, ]

Number of points per category for filtered data:

filtered_data4 %>% count(EUNISa_1)
rf_cforest4 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data4,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest4 <- predict(rf_cforest4, newdata = test_data4,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest4, test_data4$EUNISa_1)
varimp_rf_cforest4 <- party::varimp(rf_cforest4, conditional = F) 

Variable Importance Plot

varimp_rf_cforest4_df <- data.frame(Variable = names(varimp_rf_cforest4),
                                    Importance = varimp_rf_cforest4)
ggplot(varimp_rf_cforest4_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()

# Step 5: Plot ROC curves with ggplot2
roc4 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc4

All GPS points above p20 and below p80

Filter the data to get only GPS-points above p20 and below p80 of NDVI_max and NDMI_min.

# Filter rows above the 20th percentile and below the 80th percentile for both variables
filtered_data5 <- all_GPS_valid %>%
  filter(
    (NDVI_max >= percentile_20_NDVI_max & NDVI_max <= percentile_80_NDVI_max) &
    (NDMI_min >= percentile_20_NDMI_min & NDMI_min <= percentile_80_NDMI_min)
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices5 <- sample(1:nrow(filtered_data5), 0.7 * nrow(filtered_data5))
train_data5 <- filtered_data5[train_indices5, ]
test_data5 <- filtered_data5[-train_indices5, ]

Number of points per category for filtered data:

filtered_data5 %>% count(EUNISa_1)
rf_cforest5 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data5,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest5 <- predict(rf_cforest5, newdata = test_data5,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest5, test_data5$EUNISa_1)
varimp_rf_cforest5 <- party::varimp(rf_cforest5, conditional = F) 

Variable Importance Plot

varimp_rf_cforest5_df <- data.frame(Variable = names(varimp_rf_cforest5),
                                    Importance = varimp_rf_cforest5)
ggplot(varimp_rf_cforest5_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()

# Step 5: Plot ROC curves with ggplot2
roc5 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc5

HERE: Compare RF 1-5

Cordillera data

AlpineGrasslands_indices <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrassland_Sentinel_Plot_Allyear_Allmetrics.csv")
AlpineGrasslands_phen <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
AlpineGrasslands_CH <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_CanopyHeight_1m.csv")
VegetationTypes_indices <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Sentinel_Plot_AllYear_Allmetrics.csv")
VegetationTypes_phen <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
VegetationTypes_CH <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_CanopyHeight_1m.csv")
AlpineGrasslands <- AlpineGrasslands_indices %>%
  select(-`system:index`, -.geo, -Localidad) %>%
  rename(Hábitat = "H�bitat") %>% 
  full_join(AlpineGrasslands_phen  %>%
              select(-`system:index`, -.geo, -Localidad) %>%
              rename(Hábitat = "H�bitat")) %>%
  full_join(AlpineGrasslands_CH  %>%
              select(-`system:index`, -.geo, -Localidad)) %>%
  select(-Date__year, - `Precisi�n`) %>%
  mutate(DATE = ymd(DATE)) %>%
  rename(ID = "Releve_num") %>%
  mutate(ID = as.character(ID)) %>%
  mutate(layer = "AlpineGrasslands")
VegetationTypes <- VegetationTypes_indices %>%
  select(-`system:index`, -.geo) %>%
  full_join(VegetationTypes_phen  %>%
              select(-`system:index`, -.geo)) %>%
  full_join(VegetationTypes_CH  %>%
              select(-`system:index`, -.geo)) %>%
  rename(Hábitat = "TYPE") %>%
  mutate(layer = "VegetationTypes")

Merge both datasets:

cordillera <- bind_rows(
  AlpineGrasslands %>% select(DATE, ID, starts_with("NDMI"),
                              starts_with("NDVI"), Hábitat, "EOS_DOY",
                              "Peak_DOY", "SOS_DOY", "Season_Length",
                              "canopy_height", "layer"),
  VegetationTypes %>% select(DATE, ID, starts_with("NDMI"),
                              starts_with("NDVI"), Hábitat, "EOS_DOY",
                              "Peak_DOY", "SOS_DOY", "Season_Length",
                              "canopy_height", "layer")
  ) %>%
  mutate(EUNISa_1 = case_when(
    Hábitat = str_detect(Hábitat, "Pastizal|Cervunal|grassland|meadow") ~ "R",
    Hábitat = str_detect(Hábitat, "forest") ~ "T",
    Hábitat = str_detect(Hábitat, "Scrub|scrub|Shrubland|shrubland|shrub|Heathland") ~ "S",
    Hábitat = str_detect(Hábitat, "Suelo|Scree|scree|cliff") ~ "U",
    Hábitat = is.na(Hábitat) ~ "R",
    TRUE ~ NA_character_),
    EUNISa_1_descr = case_when(
      EUNISa_1 == "R" ~ "Grasslands",
      EUNISa_1 == "T" ~ "Forests and other wooded land",
      EUNISa_1 == "S" ~ "Heathlands, scrub and tundra",
      EUNISa_1 == "U" ~ "Inland habitats with no or little soil")
    )

NDVI, NDMI

distr_plot(cordillera,
           c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"), 
           c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))
distr_plot(cordillera,
           c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"), 
           c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))

Session info

sessionInfo()
LS0tDQp0aXRsZTogIlNjcmlwdCB0byB2YWxpZGF0ZSBwb2ludHMgaW4gUmVTdXJ2ZXkgZGF0YWJhc2UgdXNpbmcgUlMgZGF0YSINCnN1YnRpdGxlOiAiVmFsaWRhdGlvbiBkb25lIHdpdGggYSBzYW1wbGUgb2YgcG9pbnRzIChsYXN0IG9ic2VydmF0aW9ucykiDQphdXRob3I6ICJBbGljaWEgVmFsZMOpcyINCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCICVZJylgIg0Kb3V0cHV0Og0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBGQUxTRSkNCmBgYA0KDQpUaGlzIFIgc2NyaXB0IGlzIHVzZWQgdG8gdmFsaWRhdGUgdGhlIHBvaW50cyBpbiB0aGUgUmVTdXJ2ZXkgZGF0YWJhc2UgdXNpbmcgUlMgaW5kaWNhdG9ycyAoaW5kaWNlcyArIHBoZW5vbG9neSArIGNhbm9weSBoZWlnaHQpLg0KDQojIExvYWQgbGlicmFyaWVzDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShzY2FsZXMpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShybmF0dXJhbGVhcnRoKQ0KbGlicmFyeShkdHBseXIpDQpsaWJyYXJ5KGxtZTQpDQpsaWJyYXJ5KGxtZXJUZXN0KQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KGdnZWZmZWN0cykNCmxpYnJhcnkocGFydHkpDQpsaWJyYXJ5KHBhcnR5a2l0KQ0KbGlicmFyeShzdHJ1Y2NoYW5nZSkNCmxpYnJhcnkoZ2dwYXJ0eSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KG1vcmVwYXJ0eSkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShwUk9DKQ0KbGlicmFyeShjb3JycGxvdCkNCmBgYA0KDQojIERlZmluZSBwcmludGFsbCBmdW5jdGlvbg0KDQpgYGB7cn0NCnByaW50YWxsIDwtIGZ1bmN0aW9uKHRpYmJsZSkgew0KICBwcmludCh0aWJibGUsIHdpZHRoID0gSW5mKQ0KICB9DQpgYGANCg0KIyBMb2FkIGdlb21fZmxhdF92aW9saW4gcGxvdA0KDQpgYGB7cn0NCnNvdXJjZSgiaHR0cHM6Ly9naXN0LmdpdGh1YnVzZXJjb250ZW50LmNvbS9iZW5tYXJ3aWNrLzJhMWJiMDEzM2ZmNTY4Y2JlMjhkL3Jhdy9mYjUzYmQ5NzEyMWY3ZjljZTk0NzgzN2VmMWE0YzY1YTczYmZmYjNmL2dlb21fZmxhdF92aW9saW4uUiIpDQpgYGANCg0KIyBSZWFkIGRhdGENCg0KYGBge3J9DQpkYXRhX3ZhbGlkYXRpb248LXJlYWRfdHN2KGhlcmUoImRhdGEiLCAiY2xlYW4iLCJmaW5hbF9SU19kYXRhX2JhbmRzX1MyX3NhbXBsZS5jc3YiKSkNCmBgYA0KDQpObyBwYXJzaW5nIGlzc3VlcyENCg0KIyBTb21lIGRhdGEgbWFuYWdlbmVtdA0KDQojIyBUTy1ETzogTWlzc2luZyBkYXRhIGNoZWNrcw0KDQpEbyB3aGVuIGFsbCBSUyBkYXRhIGlzIHJlYWR5IQ0KDQojIERpc3RyaWJ1dGlvbnMgYWxsIGJpb3JlZ2lvbnMNCg0KIyMgSW5kaWNlcw0KDQpgYGB7cn0NCiMgRGVmaW5lIGEgZnVuY3Rpb24gdG8gY3JlYXRlIGhpc3RvZ3JhbXMNCnBsb3RfaGlzdG9ncmFtIDwtIGZ1bmN0aW9uKGRhdGEsIHhfdmFyLCB4X2xhYmVsKSB7DQogIGdncGxvdChkYXRhICU+JQ0KICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSwNCiAgICAgICAgIGFlcyh4ID0gISFzeW0oeF92YXIpKSkgKw0KICAgIGdlb21faGlzdG9ncmFtKGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJ3aGl0ZSIpICsNCiAgICBsYWJzKHggPSB4X2xhYmVsLCB5ID0gIkZyZXF1ZW5jeSIpICsNCiAgICB0aGVtZV9idygpDQp9DQpgYGANCg0KYGBge3J9DQojIERlZmluZSBhIGZ1bmN0aW9uIHRvIGNyZWF0ZSBwbG90cyB3aXRoIHZpb2xpbiArIGJveHBsb3QgKyBwb2ludHMNCmRpc3RyX3Bsb3QgPC0gZnVuY3Rpb24oZGF0YSwgeV92YXJzLCB5X2xhYmVscykgew0KICBmb3IgKGkgaW4gc2VxX2Fsb25nKHlfdmFycykpIHsNCiAgICB5X3ZhciA8LSB5X3ZhcnNbW2ldXQ0KICAgIHlfbGFiZWwgPC0geV9sYWJlbHNbW2ldXQ0KICAgIA0KICAgIHAgPC0gZ2dwbG90KGRhdGEgPSBkYXRhICU+JQ0KICAgICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSksDQogICAgICAgICAgICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgeSA9ICEhc3ltKHlfdmFyKSwgZmlsbCA9IEVVTklTYV8xX2Rlc2NyKSkgKw0KICAgICAgZ2VvbV9mbGF0X3Zpb2xpbihwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAwLjIsIHkgPSAwKSwgYWxwaGEgPSAwLjgpICsNCiAgICAgIGdlb21fcG9pbnQoYWVzKHkgPSAhIXN5bSh5X3ZhciksIGNvbG9yID0gRVVOSVNhXzFfZGVzY3IpLA0KICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMTUpLCBzaXplID0gMSwgYWxwaGEgPSAwLjI1KSArDQogICAgICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgICAgIHN0YXRfc3VtbWFyeShmdW4ueSA9IG1lYW4sIGdlb20gPSAicG9pbnQiLCBzaGFwZSA9IDIwLCBzaXplID0gMSkgKw0KICAgICAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZnVuY3Rpb24oeCkgZGF0YS5mcmFtZSh5ID0gbWF4KHgpICsgMC4xLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGxlbmd0aCh4KSksDQogICAgICAgICAgICAgICAgICAgZ2VvbSA9ICJ0ZXh0IiwgYWVzKGxhYmVsID0gLi5sYWJlbC4uKSwgdmp1c3QgPSAwLjUpICsNCiAgICAgIGxhYnMoeSA9IHlfbGFiZWwsIHggPSAiRVVOSVMgbGV2ZWwgMSIpICsNCiAgICAgIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgICAgIGd1aWRlcyhmaWxsID0gRkFMU0UsIGNvbG9yID0gRkFMU0UpICsNCiAgICAgIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkNCiAgICANCiAgICBwcmludChwKQ0KICB9DQp9DQpgYGANCg0KUmFuZ2VzIG9mIG1pbiBhbmQgbWF4Og0KDQpgYGB7cn0NCnJhbmdlKGRhdGFfdmFsaWRhdGlvbiRORFZJX21heCwgbmEucm0gPSBUKSAjIE5EVklfbWF4ID4gMSAoc2xpZ2h0bHkpDQpyYW5nZShkYXRhX3ZhbGlkYXRpb24kTkRNSV9tYXgsIG5hLnJtID0gVCkgIyBORE1JX21heCA+IDEgKHNsaWdodGx5KQ0KcmFuZ2UoZGF0YV92YWxpZGF0aW9uJE5EV0lfbWF4LCBuYS5ybSA9IFQpDQpyYW5nZShkYXRhX3ZhbGlkYXRpb24kU0FWSV9tYXgsIG5hLnJtID0gVCkNCnJhbmdlKGRhdGFfdmFsaWRhdGlvbiRFVklfbWF4LCBuYS5ybSA9IFQpICMgRVZJX21heCA+IDEgKHNsaWdodGx5KQ0KcmFuZ2UoZGF0YV92YWxpZGF0aW9uJE5EVklfbWluLCBuYS5ybSA9IFQpDQpyYW5nZShkYXRhX3ZhbGlkYXRpb24kTkRNSV9taW4sIG5hLnJtID0gVCkNCnJhbmdlKGRhdGFfdmFsaWRhdGlvbiRORFdJX21pbiwgbmEucm0gPSBUKSAjIE5EV0lfbWluIDwgLTEgKHNsaWdodGx5KQ0KcmFuZ2UoZGF0YV92YWxpZGF0aW9uJFNBVklfbWluLCBuYS5ybSA9IFQpDQpyYW5nZShkYXRhX3ZhbGlkYXRpb24kRVZJX21pbiwgbmEucm0gPSBUKSAjIEVWSV9taW4gPCAtMSENCmBgYA0KDQpgYGB7cn0NCm5yb3coZGF0YV92YWxpZGF0aW9uICU+JSBkcGx5cjo6ZmlsdGVyKE5EVklfbWF4ID4gMSkpDQpucm93KGRhdGFfdmFsaWRhdGlvbiAlPiUgZHBseXI6OmZpbHRlcihORE1JX21heCA+IDEpKQ0KbnJvdyhkYXRhX3ZhbGlkYXRpb24gJT4lIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA+IDEpKQ0KbnJvdyhkYXRhX3ZhbGlkYXRpb24gJT4lIGRwbHlyOjpmaWx0ZXIoTkRXSV9taW4gPCAtMSkpDQpucm93KGRhdGFfdmFsaWRhdGlvbiAlPiUgZHBseXI6OmZpbHRlcihFVklfbWluIDwgLTEpKQ0KYGBgDQoNCkhpc3RvZ3JhbXMgdG8gY2hlY2sgdGhhdCBtYXggYW5kIG1pbiB2YWx1ZXMgYXJlIG9rOg0KDQpgYGB7cn0NCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfbWF4IiwgIk5EVkkgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5ETUlfbWF4IiwgIk5ETUkgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EV0lfbWF4IiwgIk5EV0kgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIlNBVklfbWF4IiwgIlNBVkkgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9tYXgiLCAiRVZJIG1heCIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX21pbiIsICJORFZJIG1pbiIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORE1JX21pbiIsICJORE1JIG1pbiIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFdJX21pbiIsICJORFdJIG1pbiIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX21pbiIsICJTQVZJIG1pbiIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24gJT4lDQogICAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX21pbiA+IC0xLjUpLCAiRVZJX21pbiIsICJFVkkgbWluIikNCmBgYA0KDQpgYGB7cn0NCm5yb3coZGF0YV92YWxpZGF0aW9uICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA+IDEgfCBFVklfbWF4IDwgLTEpKQ0KZGF0YV92YWxpZGF0aW9uICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpJT4lDQogIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA+IDEgfCBFVklfbWF4IDwgLTEpICU+JQ0KICBjb3VudChiaW9nZW8sIHVuaXQpDQpgYGANCg0KTW9zdCBFVkkgdmFsdWVzIGFyZSBvayENCg0KRGlzdHJpYnV0aW9uIHBsb3RzOg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGlzdHJfcGxvdChkYXRhX3ZhbGlkYXRpb24gJT4lIGRwbHlyOjpmaWx0ZXIoRVZJX21pbiA+IC0wLjUpLA0KICAgICAgICAgICBjKCJORFZJX21heCIsICJFVklfbWF4IiwgIlNBVklfbWF4IiwgIk5ETUlfbWF4IiwgIk5EV0lfbWF4IiwNCiAgICAgICAgICAgICAiTkRWSV9taW4iLCAiRVZJX21pbiIsICJTQVZJX21pbiIsICJORE1JX21pbiIsICJORFdJX21pbiIpLA0KICAgICAgICAgICBjKCJORFZJX21heCIsICJFVklfbWF4IiwgIlNBVklfbWF4IiwgIk5ETUlfbWF4IiwgIk5EV0lfbWF4IiwNCiAgICAgICAgICAgICAiTkRWSV9taW4iLCAiRVZJX21pbiIsICJTQVZJX21pbiIsICJORE1JX21pbiIsICJORFdJX21pbiIpKQ0KYGBgDQoNCiMjIENIDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChkYXRhX3ZhbGlkYXRpb24sICJjYW5vcHlfaGVpZ2h0IiwgIkNhbm9weSBoZWlnaHQgKG0pIikNCmBgYA0KIA0KIyMjIFNob3cgaGFiaXRhdHMgd2l0aCBDSCBjYXRlZ29yaWVzDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgICAgICAgICMgS2VlcCBvbmx5IGZvcmVzdHMsIGdyYXNzbGFuZHMsIHNocnVibGFuZHMgYW5kIHdldGxhbmRzDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgICAgICAgIG11dGF0ZShDSF9jYXQgPQ0KICAgICAgICAgICAgICAgICAgZmFjdG9yKA0KICAgICAgICAgICAgICAgICAgICBjYXNlX3doZW4oY2Fub3B5X2hlaWdodCA9PSAwIH4gIjAgbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gMCAmIGNhbm9weV9oZWlnaHQgPD0gMSB+ICIwLTEgbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gMSAmIGNhbm9weV9oZWlnaHQgPD0yIH4gIjEtMiBtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbm9weV9oZWlnaHQgPiAyICYgY2Fub3B5X2hlaWdodCA8PTUgfiAiMi01IG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2Fub3B5X2hlaWdodCA+IDUgJiBjYW5vcHlfaGVpZ2h0IDw9OCB+ICI1LTggbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gOCB+ICI+IDggbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYShjYW5vcHlfaGVpZ2h0KSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKA0KICAgICAgICAgICAgICAgICAgICAgICIwIG0iLCAiMC0xIG0iLCAiMS0yIG0iLCAiMi01IG0iLCAiNS04IG0iLCAiPiA4IG0iKSkpLA0KICAgICAgIGFlcyh4ID0gRVVOSVNhXzFfZGVzY3IsIGZpbGwgPSBDSF9jYXQpKSArDQogIGdlb21fYmFyKCkgKyB0aGVtZV9idygpICsgY29vcmRfZmxpcCgpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGxhYmVsX251bWJlcigpKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xKSArDQogIGxhYnMoeCA9ICJFVU5JUyBsZXZlbCAxIiwgZmlsbCA9ICJDYW5vcHkgaGVpZ2h0IikgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTUpKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44LCAwLjc1KSwNCiAgICAgICAgbGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIpDQpgYGANCg0KIyMjIFN0YXRzIHBlciBoYWJpdGF0IHR5cGUNCg0KYGBge3J9DQpkYXRhX3ZhbGlkYXRpb24gJT4lDQogICMgS2VlcCBvbmx5IGZvcmVzdHMsIGdyYXNzbGFuZHMsIHNocnVibGFuZHMgYW5kIHdldGxhbmRzDQogIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBncm91cF9ieShFVU5JU2FfMV9kZXNjcikgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoY2Fub3B5X2hlaWdodCwgbGlzdCgNCiAgICBtZWFuID0gbWVhbiwNCiAgICBtZWRpYW4gPSBtZWRpYW4sDQogICAgc2QgPSBzZCwNCiAgICBtaW4gPSBtaW4sDQogICAgbWF4ID0gbWF4DQogICAgKSwgbmEucm0gPSBUUlVFKSkNCmBgYA0KDQojIyBQaGVub2xvZ3kNCg0KT25seSB1c2luZyBORFZJLSBhbmQgU0FWSS1iYXNlZCB2YWx1ZXMgc28gZmFyLg0KDQpNYXhpbXVtIE5EVkkgc2hvdWxkIGJlIGVxdWFsIHRvIHZhbHVlIGF0IHBlYWs/DQoNCmBgYHtyfQ0KbnJvdyhkYXRhX3ZhbGlkYXRpb24gJT4lIGRwbHlyOjpmaWx0ZXIoTkRWSV9wb3NfdmFsdWUgICE9IE5EVklfbWF4KSkNCmBgYA0KDQpOb3Qgc3VyZSB3aHkgdGhpcyBoYXBwZW5zLCBidXQgaW4gbW9zdCBjYXNlcyB0aGUgZGlmZmVyZW5jZSBpcyBzbWFsbCAoPCAtMC4xKS4gQW55d2F5LCB3ZSBzaG91bGQgdXNlIG9ubHkgb25lIG9mIHRoZXNlIHR3byBpbiB0aGUgUkYgbW9kZWxzLg0KDQpgYGB7cn0NCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfc29zX2RveSIsICJORFZJX3Nvc19kb3kiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiTkRWSV9wb3NfZG95IiwgIk5EVklfcG9zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX2Vvc19kb3kiLCAiTkRWSV9lb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9zb3NfZG95IiwgIkVWSV9zb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9wb3NfZG95IiwgIkVWSV9wb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9lb3NfZG95IiwgIkVWSV9lb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIlNBVklfc29zX2RveSIsICJTQVZJX3Nvc19kb3kiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiU0FWSV9wb3NfZG95IiwgIlNBVklfcG9zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX2Vvc19kb3kiLCAiU0FWSV9lb3NfZG95IikNCmBgYA0KDQpgYGB7cn0NCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfc29zX3ZhbHVlIiwgIk5EVklfc29zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfcG9zX3ZhbHVlIiwgIk5EVklfcG9zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfZW9zX3ZhbHVlIiwgIk5EVklfZW9zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9zb3NfdmFsdWUiLCAiRVZJX3Nvc192YWx1ZSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfcG9zX3ZhbHVlIiwgIkVWSV9wb3NfdmFsdWUiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiRVZJX2Vvc192YWx1ZSIsICJFVklfZW9zX3ZhbHVlIikNCmBgYA0KDQpgYGB7cn0NCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfZ3NkIiwgIk5EVklfZ3NkIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9nc2QiLCAiRVZJX2dzZCIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX2dzZCIsICJTQVZJX2dzZCIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsDQogICAgICAgICAgICAgICAiTkRWSV9kaWZmX3Bvc19zb3NfdmFsdWUiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiRVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsDQogICAgICAgICAgICAgICAiRVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsDQogICAgICAgICAgICAgICAiU0FWSV9kaWZmX3Bvc19zb3NfdmFsdWUiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiTkRWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICAgIk5EVklfZGlmZl9wb3NfZW9zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICAgIkVWSV9kaWZmX3Bvc19lb3NfdmFsdWUiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiU0FWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICAgIlNBVklfZGlmZl9wb3NfZW9zX3ZhbHVlIikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIk5EVklfZGlmZl9wb3Nfc29zX2RveSIsDQogICAgICAgICAgICAgICAiTkRWSV9kaWZmX3Bvc19zb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIkVWSV9kaWZmX3Bvc19zb3NfZG95IiwNCiAgICAgICAgICAgICAgICJFVklfZGlmZl9wb3Nfc29zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJTQVZJX2RpZmZfcG9zX3Nvc19kb3kiLA0KICAgICAgICAgICAgICAgIlNBVklfZGlmZl9wb3Nfc29zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX2RpZmZfZW9zX3Bvc19kb3kiLA0KICAgICAgICAgICAgICAgIk5EVklfZGlmZl9lb3NfcG9zX2RveSIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfZGlmZl9lb3NfcG9zX2RveSIsIA0KICAgICAgICAgICAgICAgIkVWSV9kaWZmX2Vvc19wb3NfZG95IikNCnBsb3RfaGlzdG9ncmFtKGRhdGFfdmFsaWRhdGlvbiwgIlNBVklfZGlmZl9lb3NfcG9zX2RveSIsIA0KICAgICAgICAgICAgICAgIlNBVklfZGlmZl9lb3NfcG9zX2RveSIpDQpgYGANCg0KYGBge3J9DQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJORFZJX2F1YyIsICJORFZJX2F1YyIpDQpwbG90X2hpc3RvZ3JhbShkYXRhX3ZhbGlkYXRpb24sICJFVklfYXVjIiwgIkVWSV9hdWMiKQ0KcGxvdF9oaXN0b2dyYW0oZGF0YV92YWxpZGF0aW9uLCAiU0FWSV9hdWMiLCAiU0FWSV9hdWMiKQ0KYGBgDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChkYXRhX3ZhbGlkYXRpb24sDQogICAgICAgICAgIGMoIk5EVklfc29zX3ZhbHVlIiwiTkRWSV9wb3NfdmFsdWUiLCAiTkRWSV9lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJFVklfc29zX3ZhbHVlIiwiRVZJX3Bvc192YWx1ZSIsICJFVklfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiU0FWSV9zb3NfdmFsdWUiLCAiU0FWSV9wb3NfdmFsdWUiLCAiU0FWSV9lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJORFZJX3Nvc19kb3kiLCJORFZJX3Bvc19kb3kiLCAiTkRWSV9lb3NfZG95IiwNCiAgICAgICAgICAgICAiRVZJX3Nvc19kb3kiLCJFVklfcG9zX2RveSIsICJFVklfZW9zX2RveSIsDQogICAgICAgICAgICAgIlNBVklfc29zX2RveSIsICJTQVZJX3Bvc19kb3kiLCAiU0FWSV9lb3NfZG95IiksDQogICAgICAgICAgIGMoIk5EVklfc29zX3ZhbHVlIiwiTkRWSV9wb3NfdmFsdWUiLCAiTkRWSV9lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJFVklfc29zX3ZhbHVlIiwiRVZJX3Bvc192YWx1ZSIsICJFVklfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiU0FWSV9zb3NfVmFsdWUiLCAiU0FWSV9wb3NfdmFsdWUiLCAiU0FWSV9lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJORFZJX3Nvc19kb3kiLCJORFZJX3Bvc19kb3kiLCAiTkRWSV9lb3NfZG95IiwNCiAgICAgICAgICAgICAiRVZJX3Nvc19kb3kiLCJFVklfcG9zX2RveSIsICJFVklfZW9zX2RveSIsDQogICAgICAgICAgICAgIlNBVklfc29zX2RveSIsICJTQVZJX3Bvc19kb3kiLCAiU0FWSV9lb3NfZG95IikNCiAgICAgICAgICAgKQ0KYGBgDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChkYXRhX3ZhbGlkYXRpb24sDQogICAgICAgICAgIGMoIk5EVklfZ3NkIiwiRVZJX2dzZCIsICJTQVZJX2dzZCIsDQogICAgICAgICAgICAgIk5EVklfZGlmZl9wb3Nfc29zX3ZhbHVlIiwgIkVWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJTQVZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsDQogICAgICAgICAgICAgIk5EVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwgIkVWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJTQVZJX2RpZmZfcG9zX2Vvc192YWx1ZSIsDQogICAgICAgICAgICAgIk5EVklfZGlmZl9wb3Nfc29zX2RveSIsICJFVklfZGlmZl9wb3Nfc29zX2RveSIsDQogICAgICAgICAgICAgIlNBVklfZGlmZl9wb3Nfc29zX2RveSIsDQogICAgICAgICAgICAgIk5EVklfZGlmZl9lb3NfcG9zX2RveSIsICJFVklfZGlmZl9lb3NfcG9zX2RveSIsDQogICAgICAgICAgICAgIlNBVklfZGlmZl9lb3NfcG9zX2RveSIpLA0KICAgICAgICAgICBjKCJORFZJX2dzZCIsIkVWSV9nc2QiLCAiU0FWSV9nc2QiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfcG9zX3Nvc192YWx1ZSIsICJFVklfZGlmZl9wb3Nfc29zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiU0FWSV9kaWZmX3Bvc19zb3NfVmFsdWUiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfcG9zX2Vvc192YWx1ZSIsICJFVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwNCiAgICAgICAgICAgICAiU0FWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfcG9zX3Nvc19kb3kiLCAiRVZJX2RpZmZfcG9zX3Nvc19kb3kiLA0KICAgICAgICAgICAgICJTQVZJX2RpZmZfcG9zX3Nvc19kb3kiLA0KICAgICAgICAgICAgICJORFZJX2RpZmZfZW9zX3Bvc19kb3kiLCAiRVZJX2RpZmZfZW9zX3Bvc19kb3kiLA0KICAgICAgICAgICAgICJTQVZJX2RpZmZfZW9zX3Bvc19kb3kiKQ0KICAgICAgICAgICApDQpgYGANCg0KYGBge3J9DQpkaXN0cl9wbG90KGRhdGFfdmFsaWRhdGlvbiwNCiAgICAgICAgICAgYygiTkRWSV9hdWMiLCAiRVZJX2F1YyIsICJTQVZJX2F1YyIpLA0KICAgICAgICAgICAgIGMoIk5EVklfYXVjIiwgIkVWSV9hdWMiLCAiU0FWSV9hdWMiKSkNCmBgYA0KDQojIFRCRDogRGlzdHJpYnV0aW9ucyBwZXIgYmlvcmVnaW9uDQoNCmBgYHtyfQ0KIyBEZWZpbmUgYSBmdW5jdGlvbiB0byBjcmVhdGUgcGxvdHMgd2l0aCB2aW9saW4gKyBib3hwbG90ICsgcG9pbnRzDQpkaXN0cl9wbG90X2Jpb2dlbyA8LSBmdW5jdGlvbihkYXRhLCB5X3ZhcnMsIHlfbGFiZWxzKSB7DQogIHBsb3RzIDwtIGxpc3QoKQ0KICANCiAgZm9yIChpIGluIHNlcV9hbG9uZyh5X3ZhcnMpKSB7DQogICAgeV92YXIgPC0geV92YXJzW1tpXV0NCiAgICB5X2xhYmVsIDwtIHlfbGFiZWxzW1tpXV0NCiAgICANCiAgICBwIDwtIGdncGxvdChkYXRhID0gZGF0YSAlPiUNCiAgICAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpLA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gRVVOSVNhXzFfZGVzY3IsIHkgPSAhIXN5bSh5X3ZhciksIGZpbGwgPSBFVU5JU2FfMV9kZXNjcikpICsNCiAgICAgIGdlb21fZmxhdF92aW9saW4ocG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh4ID0gMC4yLCB5ID0gMCksIGFscGhhID0gMC44KSArDQogICAgICBnZW9tX3BvaW50KGFlcyh5ID0gISFzeW0oeV92YXIpLCBjb2xvciA9IEVVTklTYV8xX2Rlc2NyKSwNCiAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yNSkgKw0KICAgICAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4yLCBvdXRsaWVyLnNoYXBlID0gTkEsIGFscGhhID0gMC41KSArDQogICAgICBzdGF0X3N1bW1hcnkoZnVuLnkgPSBtZWFuLCBnZW9tID0gInBvaW50Iiwgc2hhcGUgPSAyMCwgc2l6ZSA9IDEpICsNCiAgICAgIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4KSArIDAuMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgICAgIGdlb20gPSAidGV4dCIsIGFlcyhsYWJlbCA9IC4ubGFiZWwuLiksIHZqdXN0ID0gMC41KSArDQogICAgICBsYWJzKHkgPSB5X2xhYmVsLCB4ID0gIkVVTklTYV8xX2Rlc2NyIikgKw0KICAgICAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDE1KSkgKw0KICAgICAgZ3VpZGVzKGZpbGwgPSBGQUxTRSwgY29sb3IgPSBGQUxTRSkgKw0KICAgICAgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKSArIGZhY2V0X3dyYXAofiBiaW9nZW8pDQogICAgDQogICAgcGxvdHNbW3lfdmFyXV0gPC0gcA0KICB9DQogIA0KICByZXR1cm4ocGxvdHMpDQp9DQpgYGANCg0KIyMgSW5kaWNlcw0KDQpEaXN0cmlidXRpb24gcGxvdHM6DQoNCiMjIENIDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdF9iaW9nZW8oZGF0YV92YWxpZGF0aW9uLCAiY2Fub3B5X2hlaWdodCIsICJDYW5vcHkgaGVpZ2h0IChtKSIpDQpgYGANCg0KIyMgUGhlbm9sb2d5DQoNCiMgUkYgbW9kZWxzDQoNCmBgYHtyfQ0KdmFyc19SRiA8LSBjKA0KICAjIE1pbiB2YWx1ZXMgb2YgYWxsIGluZGljZXMNCiAgIk5EVklfbWluIiwgIkVWSV9taW4iLCAiTkRNSV9taW4iLCAiTkRXSV9taW4iLCAiU0FWSV9taW4iLA0KICAjIE1heCB2YWx1ZXMgb2YgTkRNSSBhbmQgTkRXSQ0KICAiTkRNSV9tYXgiLCAiTkRXSV9tYXgiLA0KICAjIEFVQyBvZiBORFZJLCBFVkkgYW5kIFNBVkkNCiAgIk5EVklfYXVjIiwgIkVWSV9hdWMiLCAiU0FWSV9hdWMiLA0KICAjIFZhbHVlcyBvZiBORFZJLCBFVkkgYW5kIFNBVkkgYXQgc29zLCBwb3MgYW5kIGVvcw0KICAiTkRWSV9zb3NfdmFsdWUiLCAiTkRWSV9wb3NfdmFsdWUiLCAiTkRWSV9lb3NfdmFsdWUiLA0KICAiRVZJX3Nvc192YWx1ZSIsICJFVklfcG9zX3ZhbHVlIiwgIkVWSV9lb3NfdmFsdWUiLCANCiAgIlNBVklfc29zX3ZhbHVlIiwgIlNBVklfcG9zX3ZhbHVlIiwgIlNBVklfZW9zX3ZhbHVlIiwNCiAgIyBEaWZmZXJlbmNlcyBwb3Mtc29zIGluIHZhbHVlIGFuZCBkb3kNCiAgIk5EVklfZGlmZl9wb3Nfc29zX3ZhbHVlIiwgIkVWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLCAiU0FWSV9kaWZmX3Bvc19zb3NfdmFsdWUiLA0KICAiTkRWSV9kaWZmX3Bvc19zb3NfZG95IiwgIkVWSV9kaWZmX3Bvc19zb3NfZG95IiwgIlNBVklfZGlmZl9wb3Nfc29zX2RveSIsDQogICMgRGlmZmVyZW5jZXMgcG9zLWVvcyBpbiB2YWx1ZSBhbmQgZG95DQogICJORFZJX2RpZmZfcG9zX2Vvc192YWx1ZSIsICJFVklfZGlmZl9wb3NfZW9zX3ZhbHVlIiwiU0FWSV9kaWZmX3Bvc19lb3NfdmFsdWUiLA0KICAiTkRWSV9kaWZmX2Vvc19wb3NfZG95IiwgIkVWSV9kaWZmX2Vvc19wb3NfZG95IiwgIlNBVklfZGlmZl9lb3NfcG9zX2RveSIsDQogICMgR3Jvd2luZyBzZWFzb24gZHVyYXRpb24NCiAgIk5EVklfZ3NkIiwgIkVWSV9nc2QiLCAiU0FWSV9nc2QiLA0KICAjIENhbm9weSBoZWlnaHQNCiAgImNhbm9weV9oZWlnaHQiKQ0KYGBgDQoNCiMjIFJGIHdpdGggYWxsIHBvaW50cw0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGEwIDwtIGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogICMgUmVtb3ZlIGFsbCByb3dzIHdpdGggd3JvbmcgdmFsdWVzIG9mIGluZGljZXMgKG5vdCBiZXR3ZWVuIC0xIGFuZCAxKQ0KICBkcGx5cjo6ZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5EVklfbWF4IDw9IDEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5ETUlfbWF4IDw9IDEpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKE5EV0lfbWluID49IC0xKSAlPiUNCiAgIyBSZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgdmFsdWVzDQogIGRwbHlyOjpmaWx0ZXIoaWZfYWxsKGFsbF9vZih2YXJzX1JGKSwgfiAhaXMubmEoLikpKSAlPiUNCiAgIyBLZWVwIG9ubHkgcm93cyB3aXRoIGRpZmZlcmVuY2VzID4gMA0KICBkcGx5cjo6ZmlsdGVyKGlmX2FsbChjb250YWlucygiZGlmZiIpLCB+IC54ID4gMCkpICU+JQ0KICAjIFNlbGVjdCBvbmx5IHZhcmlhYmxlcyBuZWVkZWQNCiAgc2VsZWN0KEVVTklTYV8xLCBhbGxfb2YodmFyc19SRikpDQpgYGANCg0KQ29ycmVsYXRpb24gb2YgYWxsIHZhcmlhYmxlcyB0byBiZSBpbmNsdWRlZCBpbiBSRiBtb2RlbHM6DQoNCmBgYHtyfQ0KY29ycnBsb3QoZmlsdGVyZWRfZGF0YTAgJT4lIA0KICAgICAgICAgICBzZWxlY3QoYWxsX29mKHZhcnNfUkYpKSAlPiUNCiAgICAgICAgICAgY29yKHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKSwNCiAgICAgICAgIG1ldGhvZCA9ICJjb2xvciIsIHR5cGUgPSAidXBwZXIiLCB0bC5jb2wgPSAiYmxhY2siLCB0bC5zcnQgPSA0NSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzMCA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGEwKSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhMCkpDQp0cmFpbl9kYXRhMCA8LSBmaWx0ZXJlZF9kYXRhMFt0cmFpbl9pbmRpY2VzMCwgXQ0KdGVzdF9kYXRhMCA8LSBmaWx0ZXJlZF9kYXRhMFstdHJhaW5faW5kaWNlczAsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMCAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0MCA8LSBwYXJ0eTo6Y2ZvcmVzdCgNCiAgZm9ybXVsYSA9IHJlZm9ybXVsYXRlKHZhcnNfUkYsIHJlc3BvbnNlID0gIkVVTklTYV8xIiksDQogIGRhdGEgPSB0cmFpbl9kYXRhMCwgDQogIGNvbnRyb2xzID0gY2ZvcmVzdF9jb250cm9sKG10cnkgPSByb3VuZChzcXJ0KGxlbmd0aChhbGxfb2YodmFyc19SRikpKSksICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtdHJ5ID0gc3FydCgzNSkgIyBzcXJ0IG9mIHRvdGFsIG4gdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICkgDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uc19yZl9jZm9yZXN0MCA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QwLCBuZXdkYXRhID0gdGVzdF9kYXRhMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT09CID0gVFJVRSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmZfY2ZvcmVzdDAsIHRlc3RfZGF0YTAkRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDAgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MCwgY29uZGl0aW9uYWwgPSBGKSANCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDAgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MCwgY29uZGl0aW9uYWwgPSBUKQ0KIyBjb25kaXRpb25hbCA9IFQgYWRqdXN0cyBmb3IgY29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlcw0KIyBUYWtlcyBsb25nIQ0Kc2F2ZSh2YXJpbXBfcmZfY29uZF9jZm9yZXN0MCwgZmlsZSA9ICJvYmplY3RzL3ZhcmltcF9yZl9jb25kX2Nmb3Jlc3QwLlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDBfZGYgPC0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IG5hbWVzKHZhcmltcF9yZl9jZm9yZXN0MCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbXBvcnRhbmNlID0gdmFyaW1wX3JmX2Nmb3Jlc3QwKQ0KZ2dwbG90KHZhcmltcF9yZl9jZm9yZXN0MF9kZiwNCiAgICAgICBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBjb29yZF9mbGlwKCkgKyB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmxlIEltcG9ydGFuY2UiLCB4ID0gIlZhcmlhYmxlcyIsIHkgPSAiSW1wb3J0YW5jZSIpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QwLCBuZXdkYXRhID0gdGVzdF9kYXRhMCwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMCRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jMCA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzANCmBgYA0KDQojIyBSRiB3aXRoIGFsbCBHUFMgcG9pbnRzIChkaWZmIG9yIG5vdCkNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMSA8LSBkYXRhX3ZhbGlkYXRpb24gJT4lDQogICMgU2VsZWN0IG9ubHkgR1BTIHBvaW50cw0KICBkcGx5cjo6ZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIEdQUyIgfA0KICAgICAgICAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBhcy5mYWN0b3IoRVVOSVNhXzEpKSAlPiUNCiAgIyBSZW1vdmUgYWxsIHJvd3Mgd2l0aCB3cm9uZyB2YWx1ZXMgb2YgaW5kaWNlcyAobm90IGJldHdlZW4gLTEgYW5kIDEpDQogIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRWSV9tYXggPD0gMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRNSV9tYXggPD0gMSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoTkRXSV9taW4gPj0gLTEpICU+JQ0KICAjIFJlbW92ZSByb3dzIHdpdGggbWlzc2luZyB2YWx1ZXMNCiAgZHBseXI6OmZpbHRlcihpZl9hbGwoYWxsX29mKHZhcnNfUkYpLCB+ICFpcy5uYSguKSkpICU+JQ0KICAjIEtlZXAgb25seSByb3dzIHdpdGggZGlmZmVyZW5jZXMgPiAwDQogIGRwbHlyOjpmaWx0ZXIoaWZfYWxsKGNvbnRhaW5zKCJkaWZmIiksIH4gLnggPiAwKSkgJT4lDQogICMgU2VsZWN0IG9ubHkgdmFyaWFibGVzIG5lZWRlZA0KICBzZWxlY3QoRVVOSVNhXzEsIGFsbF9vZih2YXJzX1JGKSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzMSA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGExKSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhMSkpDQp0cmFpbl9kYXRhMSA8LSBmaWx0ZXJlZF9kYXRhMVt0cmFpbl9pbmRpY2VzMSwgXQ0KdGVzdF9kYXRhMSA8LSBmaWx0ZXJlZF9kYXRhMVstdHJhaW5faW5kaWNlczEsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMSAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0MSA8LSBwYXJ0eTo6Y2ZvcmVzdCgNCiAgZm9ybXVsYSA9IHJlZm9ybXVsYXRlKHZhcnNfUkYsIHJlc3BvbnNlID0gIkVVTklTYV8xIiksDQogIGRhdGEgPSB0cmFpbl9kYXRhMSwgDQogIGNvbnRyb2xzID0gY2ZvcmVzdF9jb250cm9sKG10cnkgPSByb3VuZChzcXJ0KGxlbmd0aChhbGxfb2YodmFyc19SRikpKSksICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtdHJ5ID0gc3FydCgzNSkgIyBzcXJ0IG9mIHRvdGFsIG4gdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICkgDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uc19yZl9jZm9yZXN0MSA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT09CID0gVFJVRSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmZfY2ZvcmVzdDEsIHRlc3RfZGF0YTEkRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDEgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MSwgY29uZGl0aW9uYWwgPSBGKSANCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDEgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MSwgY29uZGl0aW9uYWwgPSBUKQ0KIyBjb25kaXRpb25hbCA9IFQgYWRqdXN0cyBmb3IgY29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlcw0KIyBUYWtlcyBsb25nIQ0Kc2F2ZSh2YXJpbXBfcmZfY29uZF9jZm9yZXN0MSwgZmlsZSA9ICJvYmplY3RzL3ZhcmltcF9yZl9jb25kX2Nmb3Jlc3QxLlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDFfZGYgPC0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IG5hbWVzKHZhcmltcF9yZl9jZm9yZXN0MSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbXBvcnRhbmNlID0gdmFyaW1wX3JmX2Nmb3Jlc3QxKQ0KZ2dwbG90KHZhcmltcF9yZl9jZm9yZXN0MV9kZiwNCiAgICAgICBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBjb29yZF9mbGlwKCkgKyB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmxlIEltcG9ydGFuY2UiLCB4ID0gIlZhcmlhYmxlcyIsIHkgPSAiSW1wb3J0YW5jZSIpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMSRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jMSA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzENCmBgYA0KDQojIyBSRiB3aXRoIC4uLg0KDQoNCiMgT0xEIEZST00gSEVSRQ0KDQojIEZpcnN0IHZhbGlkYXRpb24NCg0KRm9yIFQsIFIsIFMsIFEgaGFiaXRhdHMuDQoNCkRlZmluZSBhIHNldCBvZiBydWxlcyBmb3IgYSBmaXJzdCB2YWxpZGF0aW9uIG9mIEFMTCBSZVN1cnZleSBkYXRhLiBXZSBjYW4gY2FsbCB0aGVzZSAiRXhwZXJ0LWJhc2VkIiBydWxlcy4NCg0KTnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBSZVN1cnZleSBmcm9tIHRoZSBoYWJpdGF0cyBvZiBpbnRlcmVzdDoNCg0KYGBge3J9DQpucm93KGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSkNCmBgYA0KDQpOdW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIFJlU3VydmV5IGZyb20gdGhlIGhhYml0YXRzIG9mIGludGVyZXN0IGFuZCB3aXRoIGFsbCBSUyBkYXRhOg0KDQpgYGB7cn0NCm5yb3coZGF0YV92YWxpZGF0aW9uICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoQ0hfZGF0YSA9PSBUKSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKFJTX2RhdGEgPT1UKSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKFJTX3BoZW5fZGF0YSA9PSBUKSkNCmBgYA0KDQpgYGB7cn0NCmRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCA8LSBkYXRhX3ZhbGlkYXRpb24gJT4lDQogIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpDQpgYGANCg0KIyMgRGVmaW5lIHJ1bGVzDQoNCkNyZWF0ZSBjb2x1bW4gZm9yIGZpcnN0IHZhbGlkYXRpb24gYmFzZWQgb24gZGlmZmVyZW50IGluZGljYXRvcnMsIHdoZXJlICJ3cm9uZyIgaXMgbm90ZWQgd2hlbiB0aGUgdmFsaWRhdGlvbiBydWxlIGlzIG5vdCBtZXQuIEluY2x1ZGUgRVVOSVMxIGNvbmZ1c2lvbnMuDQoNCmBgYHtyfQ0KZGF0YV92YWxpZGF0aW9uX3RlcnJlc3RyaWFsICU+JSBjb3VudChFVU5JU2FfMSwgRVVOSVMxX2NvbmZfdHlwZSkNCmBgYA0KDQpEZWZpbmUgcnVsZXM6DQoNCmBgYHtyfQ0KZGF0YV92YWxpZGF0aW9uX3RlcnJlc3RyaWFsIDwtDQogIGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUNCiAgbXV0YXRlKA0KICAgIHZhbGlkXzFfTkRXSSA9IGNhc2Vfd2hlbigNCiAgICAgICMgUG9pbnRzIHRoYXQgYXJlIGJhc2ljYWxseSB3YXRlcg0KICAgICAgTkRXSV9tYXggPiAwLjMgfiAid3JvbmciLA0KICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgIHZhbGlkXzFfQ0ggPSBjYXNlX3doZW4oDQogICAgICAjIFQgcG9pbnRzIHdpdGggbG93IENIDQogICAgICBFVU5JU2FfMSA9PSAiVCIgJiBjYW5vcHlfaGVpZ2h0IDwgOCB+ICJ3cm9uZyIsDQogICAgICAjIFMgcG9pbnRzIHdpdGggbG93IENIDQogICAgICBFVU5JU2FfMSA9PSJTIiAmIGNhbm9weV9oZWlnaHQgPCA1IH4gIndyb25nIiwNCiAgICAgICMgUiAmIFEgcG9pbnRzIHdpdGggaGlnaCBDSA0KICAgICAgRVVOSVNhXzEgJWluJSBjKCJSIiwgIlEiKSAmIGNhbm9weV9oZWlnaHQgPiAyIH4gIndyb25nIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICB2YWxpZF8xX05EVkkgPSBjYXNlX3doZW4oDQogICAgICAjIFQgcG9pbnRzIHdpdGggbG93IE5EVklfbWF4DQogICAgICBFVU5JU2FfMSA9PSAiVCIgJiBORFZJX21heCA8IDAuNiB+ICJ3cm9uZyIsDQogICAgICAjIFMtUi1RIHBvaW50cyB3aXRoIGxvdyBORFZJX21heA0KICAgICAgRVVOSVNhXzEgJWluJSBjKCJSIiwgIlMiLCAiUSIpICYgTkRWSV9tYXggPCAwLjIgfiAid3JvbmciLA0KICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgICMgQ291bnQgaG93IG1hbnkgdmFsaWRhdGlvbiBydWxlcyBhcmUgbm90IG1ldA0KICAgIHZhbGlkXzFfY291bnQgPSByb3dTdW1zKGFjcm9zcyhjKHZhbGlkXzFfTkRXSSwgdmFsaWRfMV9DSCwgdmFsaWRfMV9ORFZJKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIH4gLiA9PSAid3JvbmciKSwgbmEucm0gPSBUUlVFKSwNCiAgICAjIFBvaW50cyB3aGVyZSBhdCBsZWFzdCAxIHJ1bGUgbm90IG1ldA0KICAgIHZhbGlkXzEgPSBpZl9lbHNlKHZhbGlkXzFfY291bnQgPiAwLCAiQXQgbGVhc3QgMSBydWxlIGJyb2tlbiIsDQogICAgICAgICAgICAgICAgICAgICAgIk5vIHJ1bGVzIGJyb2tlbiBzbyBmYXIiKQ0KICAgICkNCmBgYA0KDQojIyBQbG90cyBmaXJzdCB2YWxpZGF0aW9uDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCU+JQ0KICAgICAgICAgbXV0YXRlKHJ1bGVzX2Jyb2tlbiA9IGNhc2Vfd2hlbigNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAxICYgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgfiAiTkRXSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMSAmIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciIH4gIk5EVkkiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDEgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyIgfiAiQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciICYgdmFsaWRfMV9ORFZJID09ICJ3cm9uZyJ+ICJORFdJICsgTkRWSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMiAmDQogICAgICAgICAgICAgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyJ+ICJORFdJICsgQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciICYgdmFsaWRfMV9DSCA9PSAid3JvbmcifiAiTkRWSSArIENIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAzIH4gIk5EV0kgKyBORFZJICsgQ0giLA0KICAgICAgICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXw0KICAgICAgICAgKSksIA0KICAgICAgIGFlcyh4ID0gdmFsaWRfMV9jb3VudCwgZmlsbCA9IHJ1bGVzX2Jyb2tlbikpICsNCiAgZ2VvbV9iYXIoKSArIGxhYnMoeCA9ICJOdW1iZXIgb2YgYnJva2VuIHJ1bGVzIikNCmBgYA0KDQpgYGB7cn0NCmRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUNCiAgICAgICAgIG11dGF0ZShydWxlc19icm9rZW4gPSBjYXNlX3doZW4oDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMSAmIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciIH4gIk5EV0kiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDEgJiB2YWxpZF8xX05EVkkgPT0gIndyb25nIiB+ICJORFZJIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAxICYgdmFsaWRfMV9DSCA9PSAid3JvbmciIH4gIkNIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAyICYNCiAgICAgICAgICAgICB2YWxpZF8xX05EV0kgPT0gIndyb25nIiAmIHZhbGlkXzFfTkRWSSA9PSAid3JvbmcifiAiTkRXSSArIE5EVkkiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciICYgdmFsaWRfMV9DSCA9PSAid3JvbmcifiAiTkRXSSArIENIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAyICYNCiAgICAgICAgICAgICB2YWxpZF8xX05EVkkgPT0gIndyb25nIiAmIHZhbGlkXzFfQ0ggPT0gIndyb25nIn4gIk5EVkkgKyBDSCIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMyB+ICJORFdJICsgTkRWSSArIENIIiwNCiAgICAgICAgICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8NCiAgICAgICAgICkpICU+JQ0KICBjb3VudChydWxlc19icm9rZW4sIEVVTklTMV9jb25mX3R5cGUpDQpgYGANCg0KUHJvcG9ydGlvbiBvZiBvYnNlcnZhdGlvbnMgbm90IHZhbGlkYXRlZCAoc28gZmFyKToNCg0KYGBge3J9DQpucm93KGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUgZHBseXI6OmZpbHRlcih2YWxpZF8xX2NvdW50ID4gMCkpLw0KICBucm93KGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCkNCmBgYA0KDQpCdXQgYmUgYXdhcmUgdGhhdCB0aGVyZSBhcmUgc3RpbGwgTUFOWSBtaXNzaW5nIFJTIGRhdGEuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUNCiAgICAgICAgIG11dGF0ZShkaWZmX0dQUyA9IGlmX2Vsc2UoDQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgICE9ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIHwNCiAgICAgICAgICAgICBpcy5uYShgTG9jYXRpb24gbWV0aG9kYCksICJubyIsICJ5ZXMiKSksIA0KICAgICAgIGFlcyh4ID0gZGlmZl9HUFMsIGZpbGwgPSB2YWxpZF8xKSkgKw0KICBnZW9tX2JhcigpICsgbGFicyh4ID0gIkRpZmZlcmVudGlhbCBHUFMiKQ0KZ2dwbG90KGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUNCiAgICAgICAgIG11dGF0ZShHUFMgPSBjYXNlX3doZW4oDQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIH4gInllcyIsDQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIEdQUyIgfiAieWVzIiwNCiAgICAgICAgICAgaXMubmEoYExvY2F0aW9uIG1ldGhvZGApIH4gIm5vIiwNCiAgICAgICAgICAgVFJVRSB+ICJubyINCiAgICAgICAgICkpLCANCiAgICAgICBhZXMoeCA9IEdQUywgZmlsbCA9IHZhbGlkXzEpKSArDQogIGdlb21fYmFyKCkgKyBsYWJzKHggPSAiR1BTIikNCmBgYA0KDQpQb2ludHMgd2l0aCBhbnkgcnVsZSBicm9rZW4gYW5kIGNvbmZ1c2lvbiBiZXR3ZWVuIEVVTklTOg0KDQpgYGB7cn0NCm5yb3coZGF0YV92YWxpZGF0aW9uX3RlcnJlc3RyaWFsICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVMxX2NvbmYgPT0gVCAmIHZhbGlkXzFfY291bnQgPiAwKSkNCmBgYA0KDQpDb252ZXJ0IHRvIHNocCB0byBsb29rIGF0IHRoZXNlIGluIEdJUzoNCg0KYGBge3J9DQojIHN0X3dyaXRlKGRhdGFfdmFsaWRhdGlvbl90ZXJyZXN0cmlhbCAlPiUNCiMgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTMV9jb25mID09IFQgJiB2YWxpZF8xX2NvdW50ID4gMCkgJT4lDQojICAgICAgICAgICAgc3RfYXNfc2YoY29vcmRzID0gYygiTG9uX3VwZGF0ZWQiLCAiTGF0X3VwZGF0ZWQiKSwgY3JzID0gNDMyNiksDQojICAgICAgICAgICJDOi9HSVMvTU9USVZBVEUvc2hhcGVmaWxlcy9yZXN1cnZfbm90X3ZhbF9FVU5JU19jb25mLnNocCIpDQpgYGANCg0KQ2hlY2tlZCBhbmQgeWVzDQoNCkhvdyBtYW55IHBvaW50cyB3aXRoIGRpZmZlcmVudGlhbCBHUFMgdGhhdCBoYXZlIGF0IGxlYXN0IDEgcnVsZSBicm9rZW4/DQoNCmBgYHtyfQ0KbnJvdyhkYXRhX3ZhbGlkYXRpb25fdGVycmVzdHJpYWwgJT4lDQogIGRwbHlyOjpmaWx0ZXIoYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgJg0KICAgICAgICAgICB2YWxpZF8xID09ICJBdCBsZWFzdCAxIHJ1bGUgYnJva2VuIikpDQpgYGANCg0KQ29udmVydCB0byBzaHAgdG8gbG9vayBhdCB0aGVzZSBpbiBHSVM6DQoNCmBgYHtyfQ0KIyBzdF93cml0ZShkYXRhX3ZhbGlkYXRpb25fdGVycmVzdHJpYWwgJT4lDQojICAgICAgICAgICAgZHBseXI6OmZpbHRlcihgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiAmDQojICAgICAgICAgICAgICAgICAgICAgdmFsaWRfMSA9PSAiQXQgbGVhc3QgMSBydWxlIGJyb2tlbiIpICU+JQ0KIyAgICAgICAgICAgIHN0X2FzX3NmKGNvb3JkcyA9IGMoIkxvbl91cGRhdGVkIiwgIkxhdF91cGRhdGVkIiksIGNycyA9IDQzMjYpLA0KIyAgICAgICAgICAiQzovR0lTL01PVElWQVRFL3NoYXBlZmlsZXMvcmVzdXJ2X25vdF92YWxfZGlmZl9HUFMuc2hwIikNCmBgYA0KDQojIE1hcHMNCg0KIyMgUG9pbnRzIEdQUw0KDQpgYGB7cn0NCiMgTG9hZCB3b3JsZCBib3VuZGFyaWVzDQp3b3JsZCA8LSBuZV9jb3VudHJpZXMoc2NhbGUgPSAibWVkaXVtIiwgcmV0dXJuY2xhc3MgPSAic2YiKQ0KDQojIENhbGN1bGF0ZSB0aGUgZXh0ZW50IG9mIHRoZSBwb2ludHMNCnBvaW50c19HUFNfZXh0ZW50IDwtIGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgZHBseXI6OmZpbHRlcihgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiB8DQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIEdQUyIpICU+JQ0KICBzdW1tYXJpc2UobG9uX21pbiA9IG1pbihMb25fdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxvbl9tYXggPSBtYXgoTG9uX3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsYXRfbWluID0gbWluKExhdF91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbGF0X21heCA9IG1heChMYXRfdXBkYXRlZCwgbmEucm0gPSBUUlVFKSkNCg0KIyBBZGQgcGFkZGluZyB0byB0aGUgZXh0ZW50IChhZGp1c3QgYXMgbmVlZGVkKQ0KcGFkZGluZyA8LSAyICAjIEFkanVzdCBwYWRkaW5nIHRvIHlvdXIgcHJlZmVyZW5jZQ0KeF9saW1pdHMgPC0gYyhwb2ludHNfR1BTX2V4dGVudCRsb25fbWluIC0gcGFkZGluZywNCiAgICAgICAgICAgICAgcG9pbnRzX0dQU19leHRlbnQkbG9uX21heCArIHBhZGRpbmcpDQp5X2xpbWl0cyA8LSBjKHBvaW50c19HUFNfZXh0ZW50JGxhdF9taW4gLSBwYWRkaW5nLA0KICAgICAgICAgICAgICBwb2ludHNfR1BTX2V4dGVudCRsYXRfbWF4ICsgcGFkZGluZykNCg0KIyBDcmVhdGUgdGhlIHpvb21lZCBtYXANCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gd29ybGQsIGZpbGwgPSAibGlnaHRibHVlIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0YV92YWxpZGF0aW9uICU+JQ0KICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUICkgJT4lDQogICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIHwNCiAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggR1BTIiksDQogICAgICAgICAgICAgYWVzKHggPSBMb25fdXBkYXRlZCwgeSA9IExhdF91cGRhdGVkLCBjb2xvciA9IEVVTklTYV8xKSwNCiAgICAgICAgICAgICBzaXplID0gMSkgKw0KICBjb29yZF9zZih4bGltID0geF9saW1pdHMsIHlsaW0gPSB5X2xpbWl0cykgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpOdW1iZXIgb2YgR1BTIHBvaW50cyBieSBDb3VudHJ5Og0KDQpgYGB7cn0NCmRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgZHBseXI6OmZpbHRlcihgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiB8DQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIEdQUyIpICU+JQ0KICBjb3VudChDb3VudHJ5KQ0KYGBgDQoNCiMjIFBvaW50cyBSZVN1cnZleQ0KDQpgYGB7cn0NCiMgQ2FsY3VsYXRlIHRoZSBleHRlbnQgb2YgdGhlIHBvaW50cw0KcG9pbnRzX3Jlc3VydmV5X2V4dGVudCA8LSBkYXRhX3ZhbGlkYXRpb24gJT4lDQogIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUICkgJT4lDQogIHN1bW1hcmlzZShsb25fbWluID0gbWluKExvbl91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbG9uX21heCA9IG1heChMb25fdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxhdF9taW4gPSBtaW4oTGF0X3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsYXRfbWF4ID0gbWF4KExhdF91cGRhdGVkLCBuYS5ybSA9IFRSVUUpKQ0KDQojIEFkZCBwYWRkaW5nIHRvIHRoZSBleHRlbnQgKGFkanVzdCBhcyBuZWVkZWQpDQpwYWRkaW5nIDwtIDIgICMgQWRqdXN0IHBhZGRpbmcgdG8geW91ciBwcmVmZXJlbmNlDQp4X2xpbWl0cyA8LSBjKHBvaW50c19yZXN1cnZleV9leHRlbnQkbG9uX21pbiAtIHBhZGRpbmcsDQogICAgICAgICAgICAgIHBvaW50c19yZXN1cnZleV9leHRlbnQkbG9uX21heCArIHBhZGRpbmcpDQp5X2xpbWl0cyA8LSBjKHBvaW50c19yZXN1cnZleV9leHRlbnQkbGF0X21pbiAtIHBhZGRpbmcsDQogICAgICAgICAgICAgIHBvaW50c19yZXN1cnZleV9leHRlbnQkbGF0X21heCArIHBhZGRpbmcpDQoNCiMgQ3JlYXRlIHRoZSB6b29tZWQgbWFwDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHdvcmxkLCBmaWxsID0gImxpZ2h0Ymx1ZSIsIGNvbG9yID0gImdyYXkiKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApLA0KICAgICAgICAgICAgIGFlcyh4ID0gTG9uX3VwZGF0ZWQsIHkgPSBMYXRfdXBkYXRlZCwgY29sb3IgPSBFVU5JU2FfMSksDQogICAgICAgICAgICAgc2l6ZSA9IDEpICsNCiAgY29vcmRfc2YoeGxpbSA9IHhfbGltaXRzLCB5bGltID0geV9saW1pdHMpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KTnVtYmVyIG9mIFJlU3VydmV5IHBvaW50cyBieSBDb3VudHJ5Og0KDQpgYGB7cn0NCmRhdGFfdmFsaWRhdGlvbiAlPiUNCiAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSAlPiUNCiAgY291bnQoQ291bnRyeSkNCmBgYA0KDQojIERpc3RyaWJ1dGlvbnMgZnJvbSBHUFMgcG9pbnRzIHdpdGhvdXQgcnVsZXMgYnJva2VuIHNvIGZhcg0KDQpDcmVhdGUgdGliYmxlIHdpdGggZGlmZmVyZW50aWFsIEdQUyBwb2ludHMgd2l0aG91dCBydWxlcyBicm9rZW4gc28gZmFyOg0KDQpgYGB7cn0NCmFsbF9HUFNfdmFsaWQgPC0gZGF0YV92YWxpZGF0aW9uX3RlcnJlc3RyaWFsICU+JQ0KICBkcGx5cjo6ZmlsdGVyKChgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiB8IA0KICAgICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggR1BTIiApICYNCiAgICAgICAgICAgdmFsaWRfMSA9PSAiTm8gcnVsZXMgYnJva2VuIHNvIGZhciIpIA0KYGBgDQoNCiMjIE5EVkksIE5ETUksIE5EV0ksIFNBVkkgYW5kIEVWSQ0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgYygiTkRWSV9tYXgiLCAiTkRWSV9wOTAiLCAiTkRWSV9taW4iLCAiTkRWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5EVkkgbWF4IiwgIk5EVkkgcDkwIiwgIk5EVkkgbWluIiwgIk5EVkkgcDEwIikpDQpkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQsDQogICAgICAgICAgIGMoIk5ETUlfbWF4IiwgIk5ETUlfcDkwIiwgIk5ETUlfbWluIiwgIk5ETUlfcDEwIiksIA0KICAgICAgICAgICBjKCJORE1JIG1heCIsICJORE1JIHA5MCIsICJORE1JIG1pbiIsICJORE1JIHAxMCIpKQ0KZGlzdHJfcGxvdChhbGxfR1BTX3ZhbGlkLA0KICAgICAgICAgICBjKCJORFdJX21heCIsICJORFdJX3A5MCIsICJORFdJX21pbiIsICJORFdJX3AxMCIpLCANCiAgICAgICAgICAgYygiTkRXSSBtYXgiLCAiTkRXSSBwOTAiLCAiTkRXSSBtaW4iLCAiTkRXSSBwMTAiKSkNCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgYygiU0FWSV9tYXgiLCAiU0FWSV9wOTAiLCAiU0FWSV9taW4iLCAiU0FWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIlNBVkkgbWF4IiwgIlNBVkkgcDkwIiwgIlNBVkkgbWluIiwgIlNBVkkgcDEwIikpDQpkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQgJT4lDQogICAgICAgICAgICAgZHBseXI6OmZpbHRlcihFVklfbWF4IDw9IDEpICU+JQ0KICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX21pbiA+PSAtMSAmIEVWSV9taW4gPD0gMSksDQogICAgICAgICAgIGMoIkVWSV9tYXgiLCAiRVZJX3A5MCIsICJFVklfbWluIiwgIkVWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIkVWSSBtYXgiLCAiRVZJIHA5MCIsICJFVkkgbWluIiwgIkVWSSBwMTAiKSkNCmBgYA0KDQojIyBDSA0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwgImNhbm9weV9oZWlnaHQiLCAiQ2Fub3B5IGhlaWdodCAobSkiKQ0KYGBgDQoNCiMjIFBoZW5vbG9neQ0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgYygiU09TX0RPWSIsIlBlYWtfRE9ZIiwgIkVPU19ET1kiLA0KICAgICAgICAgICAgICJORFZJX2F0X1NPUyIsICJORFZJX2F0X1BlYWsiLCAiTkRWSV9hdF9FT1MiLA0KICAgICAgICAgICAgICJkaWZmX1BlYWtfU09TIiwiZGlmZl9QZWFrX0VPUyIsICJTZWFzb25fTGVuZ3RoIiksDQogICAgICAgICAgIGMoIlNPUyBET1kiLCAiUGVhayBET1kiLCAiRU9TIERPWSIsDQogICAgICAgICAgICAgIk5EVkkgYXQgU09TIiwgIk5EVkkgYXQgUGVhayIsICJORFZJIGF0IEVPUyIsDQogICAgICAgICAgICAgIkRpZmZlcmVuY2UgUGVhay1TT1MiLCAiRGlmZmVyZW5jZSBQZWFrLUVPUyIsICJTZWFzb24gTGVuZ3RoIikpDQpgYGANCg0KIyBHUFMgdmFsaWQgcG9pbnRzIGFib3ZlIHAyMCBvZiBORFZJX21heCBhbmQgTkRNSV9taW4gZm9yIGVhY2ggaGFiaXRhdA0KDQojIEhFUkUhIA0KDQpDaG9zZW4gTkRWSV9taW4gYmVjYXVzZSBpdCB3YXMgaW1wb3J0YW50IGluIFJGIG1vZGVscywgYnV0IGxldCdzIHNlZSB3aXRoIG5ldyBkYXRhIQ0KDQpgYGB7cn0NCnBlcmNlbnRpbGVzX2FsbF9HUFMgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzEpICU+JQ0KICBzdW1tYXJpemUocGVyY2VudGlsZV8yMF9ORFZJX21heCA9IHF1YW50aWxlKE5EVklfbWF4LCBwcm9icyA9IDAuMjAsIG5hLnJtID0gVCksDQogICAgICAgICAgICBwZXJjZW50aWxlXzIwX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIHByb2JzID0gMC4yMCwgbmEucm0gPSBUKSkNCg0KYWxsX0dQU192YWxpZCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBsZWZ0X2pvaW4ocGVyY2VudGlsZXNfYWxsX0dQUywgYnkgPSAiRVVOSVNhXzEiKSAlPiUNCiAgbXV0YXRlKGNhdGVnb3J5X05EVklfbWF4ID0gY2FzZV93aGVuKA0KICAgIE5EVklfbWF4IDwgcGVyY2VudGlsZV8yMF9ORFZJX21heCB+ICJiZWxvd18yMHRoIiwNCiAgICBORFZJX21heCA+PSBwZXJjZW50aWxlXzIwX05EVklfbWF4IH4gImFib3ZlXzIwdGgiKSwNCiAgY2F0ZWdvcnlfTkRNSV9taW4gPSBjYXNlX3doZW4oDQogICAgTkRNSV9taW4gPCBwZXJjZW50aWxlXzIwX05ETUlfbWluIH4gImJlbG93XzIwdGgiLA0KICAgIE5ETUlfbWluID49IHBlcmNlbnRpbGVfMjBfTkRNSV9taW4gfiAiYWJvdmVfMjB0aCIpKQ0KDQpnZ3Bsb3QoZGF0YSA9IGFsbF9HUFNfdmFsaWQsDQogICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgeSA9IE5EVklfbWF4KSkgKw0KICBnZW9tX2ZsYXRfdmlvbGluKHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMiwgeSA9IDApLCBhbHBoYSA9IDAuOCwNCiAgICAgICAgICAgICAgICAgICBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjYXRlZ29yeV9ORFZJX21heCksDQogICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yNSkgKw0KICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAxKSArDQogIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4KSArIDAuMSwgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgZ2VvbSA9ICJ0ZXh0IiwgYWVzKGxhYmVsID0gLi5sYWJlbC4uKSwgdmp1c3QgPSAwLjUpICsNCiAgbGFicyh5ID0gIk5EVkkgbWF4IiwgeCA9ICJFVU5JUyBsZXZlbCAxIikgKw0KICBndWlkZXMoZmlsbCA9IEZBTFNFLCBjb2xvciA9IEZBTFNFKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImJlbG93XzIwdGgiID0gImdyZXkiLCAiYWJvdmVfMjB0aCIgPSAibGlnaHRibHVlIikpICsNCiAgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKQ0KDQpnZ3Bsb3QoZGF0YSA9IGFsbF9HUFNfdmFsaWQsDQogICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgeSA9IE5ETUlfbWluKSkgKw0KICBnZW9tX2ZsYXRfdmlvbGluKHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMiwgeSA9IDApLCBhbHBoYSA9IDAuOCwNCiAgICAgICAgICAgICAgICAgICBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjYXRlZ29yeV9ORE1JX21pbiksDQogICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yNSkgKw0KICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAxKSArDQogIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4KSArIDAuMSwgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgZ2VvbSA9ICJ0ZXh0IiwgYWVzKGxhYmVsID0gLi5sYWJlbC4uKSwgdmp1c3QgPSAwLjUpICsNCiAgbGFicyh5ID0gIk5ETUkgbWluIiwgeCA9ICJFVU5JUyBsZXZlbCAxIikgKw0KICBndWlkZXMoZmlsbCA9IEZBTFNFLCBjb2xvciA9IEZBTFNFKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImJlbG93XzIwdGgiID0gImdyZXkiLCAiYWJvdmVfMjB0aCIgPSAibGlnaHRibHVlIikpICsNCiAgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCiMgUkYgbW9kZWxzDQoNClVzaW5nIHRoZSBjb25kaXRpb25hbCBpbmZlcmVuY2UgdmVyc2lvbiBvZiByYW5kb20gZm9yZXN0IChjZm9yZXN0IGluIHBhY2thZ2UgcGFydHkpLiBTdWdnZXN0ZWQgaWYgdGhlIGRhdGEgYXJlIGhpZ2hseSBjb3JyZWxhdGVkLiBDZm9yZXN0IGlzIG1vcmUgc3RhYmxlIGluIGRlcml2aW5nIHZhcmlhYmxlIGltcG9ydGFuY2UgdmFsdWVzIGluIHRoZSBwcmVzZW5jZSBvZiBoaWdobHkgY29ycmVsYXRlZCB2YXJpYWJsZXMsIHRodXMgcHJvdmlkaW5nIGJldHRlciBhY2N1cmFjeSBpbiBjYWxjdWxhdGluZyB2YXJpYWJsZSBpbXBvcnRhbmNlIChyZWYgYmVsb3cpLg0KDQpIb3Rob3JuLCBULiwgSG9ybmlrLCBLLiBhbmQgWmVpbGVpcywgQS4gKDIwMDYpIFVuYmlhc2VkIFJlY3Vyc2l2ZSBQb3J0aW9uaW5nOiBBIENvbmRpdGlvbmFsIEluZmVyZW5jZSBGcmFtZXdvcmsuIEpvdXJuYWwgb2YgQ29tcHV0YXRpb25hbCBhbmQgR3JhcGhpY2FsIFN0YXRpc3RpY3MsIDE1LCA2NTEtDQo2NzQuIGh0dHA6Ly9keC5kb2kub3JnLzEwLjExOTgvMTA2MTg2MDA2WDEzMzkzMw0KDQojIyBBbGwgR1BTIHBvaW50cw0KDQpgYGB7cn0NCmRwbHlyOjpmaWx0ZXJlZF9kYXRhMCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBkcGx5cjo6ZmlsdGVyKCFpcy5uYShORFZJX21heCkgJiAhaXMubmEoTkRNSV9tYXgpICYgIWlzLm5hKE5EV0lfbWF4KSAmDQogICAgICAgICAgICFpcy5uYShTQVZJX21heCkgJiAhaXMubmEoRVZJX21heCkgJiAhaXMubmEoTkRWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKE5ETUlfbWluKSAmICFpcy5uYShORFdJX21pbikgJiAhaXMubmEoU0FWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKEVWSV9taW4pKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzMCA8LSBzYW1wbGUoMTpucm93KGRwbHlyOjpmaWx0ZXJlZF9kYXRhMCksIDAuNyAqIG5yb3coZHBseXI6OmZpbHRlcmVkX2RhdGEwKSkNCnRyYWluX2RhdGEwIDwtIGRwbHlyOjpmaWx0ZXJlZF9kYXRhMFt0cmFpbl9pbmRpY2VzMCwgXQ0KdGVzdF9kYXRhMCA8LSBkcGx5cjo6ZmlsdGVyZWRfZGF0YTBbLXRyYWluX2luZGljZXMwLCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGRwbHlyOjpmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmRwbHlyOjpmaWx0ZXJlZF9kYXRhMCAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0MCA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QwIDwtIHByZWRpY3QocmZfY2ZvcmVzdDAsIG5ld2RhdGEgPSB0ZXN0X2RhdGEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0MCwgdGVzdF9kYXRhMCRFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0MCA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QwLCBjb25kaXRpb25hbCA9IEYpIA0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmZfY29uZF9jZm9yZXN0MCA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QwLCBjb25kaXRpb25hbCA9IFQpDQojIGNvbmRpdGlvbmFsID0gVCBhZGp1c3RzIGZvciBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQojIFRha2VzIGxvbmchDQpzYXZlKHZhcmltcF9yZl9jb25kX2Nmb3Jlc3QwLCBmaWxlID0gIm9iamVjdHMvdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDAuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0MF9kZiA8LSBkYXRhLmZyYW1lKFZhcmlhYmxlID0gbmFtZXModmFyaW1wX3JmX2Nmb3Jlc3QwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEltcG9ydGFuY2UgPSB2YXJpbXBfcmZfY2ZvcmVzdDApDQpnZ3Bsb3QodmFyaW1wX3JmX2Nmb3Jlc3QwX2RmLA0KICAgICAgIGFlcyh4ID0gcmVvcmRlcihWYXJpYWJsZSwgSW1wb3J0YW5jZSksIHkgPSBJbXBvcnRhbmNlKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGNvb3JkX2ZsaXAoKSArIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSIsIHggPSAiVmFyaWFibGVzIiwgeSA9ICJJbXBvcnRhbmNlIikNCmBgYA0KDQpST0MgY3VydmVzOg0KDQpgYGB7cn0NCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzIGZvciBlYWNoIGNsYXNzDQpwcm9iYWJpbGl0aWVzIDwtIHByZWRpY3QocmZfY2ZvcmVzdDAsIG5ld2RhdGEgPSB0ZXN0X2RhdGEwLCB0eXBlID0gInByb2IiKQ0KDQojIFN0ZXAgMTogQ29udmVydCBsaXN0IG9mIG1hdHJpY2VzIHRvIGEgcHJvcGVyIGRhdGEgZnJhbWUNCnByb2JfbWF0cml4IDwtIHQoc2FwcGx5KHByb2JhYmlsaXRpZXMsIGFzLnZlY3RvcikpDQpjb2xuYW1lcyhwcm9iX21hdHJpeCkgPC0gYygiUSIsICJSIiwgIlMiLCAiVCIpICAjIEFkanVzdCBpZiBuZWVkZWQNCnByb2JfZGYgPC0gYXMuZGF0YS5mcmFtZShwcm9iX21hdHJpeCkNCg0KIyBTdGVwIDI6IFByZXBhcmUgYWN0dWFsIGNsYXNzIGxhYmVscw0KYWN0dWFsIDwtIGZhY3Rvcih0ZXN0X2RhdGEwJEVVTklTYV8xLCBsZXZlbHMgPSBjKCJRIiwgIlIiLCAiUyIsICJUIikpDQpjbGFzc2VzIDwtIGxldmVscyhhY3R1YWwpDQoNCiMgU3RlcCAzOiBCaW5hcml6ZSBhY3R1YWwgbGFiZWxzDQphY3R1YWxfYmluIDwtIG1vZGVsLm1hdHJpeCh+IGFjdHVhbCAtIDEpDQpjb2xuYW1lcyhhY3R1YWxfYmluKSA8LSBnc3ViKCJhY3R1YWwiLCAiIiwgY29sbmFtZXMoYWN0dWFsX2JpbikpDQoNCiMgU3RlcCA0OiBDb21wdXRlIFJPQyBkYXRhIGZvciBlYWNoIGNsYXNzIHdpdGggQVVDIGluIGxhYmVsDQpyb2NfZGF0YSA8LSBsYXBwbHkoY2xhc3NlcywgZnVuY3Rpb24oY2xhc3MpIHsNCiAgcm9jX29iaiA8LSByb2MoYWN0dWFsX2JpblssIGNsYXNzXSwgcHJvYl9kZltbY2xhc3NdXSkNCiAgYXVjX3ZhbCA8LSByb3VuZChhdWMocm9jX29iaiksIDMpDQogIGRhdGEuZnJhbWUoDQogICAgRlBSID0gcmV2KHJvY19vYmokc3BlY2lmaWNpdGllcyksDQogICAgVFBSID0gcmV2KHJvY19vYmokc2Vuc2l0aXZpdGllcyksDQogICAgQ2xhc3MgPSBwYXN0ZTAoY2xhc3MsICIgKEFVQyA9ICIsIGF1Y192YWwsICIpIikNCiAgKQ0KfSkgJT4lIGJpbmRfcm93cygpDQoNCiMgU3RlcCA1OiBQbG90IFJPQyBjdXJ2ZXMgd2l0aCBnZ3Bsb3QyDQpyb2MwIDwtIGdncGxvdChyb2NfZGF0YSwgYWVzKHggPSBGUFIsIHkgPSBUUFIsIGNvbG9yID0gQ2xhc3MpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yKSArDQogIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyYXkiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTXVsdGljbGFzcyBST0MgQ3VydmVzIHdpdGggQVVDIiwNCiAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLA0KICAgIHkgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICBjb2xvciA9ICJDbGFzcyAoQVVDKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0Kcm9jMA0KYGBgDQoNCiMjIFJFVklTRSBGUk9NIEhFUkU6IEFsbCBHUFMgcG9pbnRzIGFib3ZlIHAyMA0KDQpkcGx5cjo6ZmlsdGVyIHRoZSBkYXRhIHRvIGdldCBvbmx5IEdQUy1wb2ludHMgYWJvdmUgcDIwIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbi4NCg0KYGBge3J9DQphbGxfR1BTX3ZhbGlkIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIHNlbGVjdCgtcGVyY2VudGlsZV8yMF9ORFZJX21heCwgLXBlcmNlbnRpbGVfMjBfTkRNSV9taW4pDQpgYGANCg0KYGBge3J9DQpwZXJjZW50aWxlcyA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBncm91cF9ieShFVU5JU2FfMSkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBwZXJjZW50aWxlXzIwX05EVklfbWF4ID0gcXVhbnRpbGUoTkRWSV9tYXgsIDAuMjAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV8yMF9ORE1JX21pbiA9IHF1YW50aWxlKE5ETUlfbWluLCAwLjIwLCBuYS5ybSA9IFQpLA0KICAgIHBlcmNlbnRpbGVfODBfTkRWSV9tYXggPSBxdWFudGlsZShORFZJX21heCwgMC44MCwgbmEucm0gPSBUKSwNCiAgICBwZXJjZW50aWxlXzgwX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIDAuODAsIG5hLnJtID0gVCkNCiAgICApDQoNCiMgSm9pbiB0aGUgcGVyY2VudGlsZXMgYmFjayB0byB0aGUgb3JpZ2luYWwgZGF0YQ0KYWxsX0dQU192YWxpZCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBsZWZ0X2pvaW4ocGVyY2VudGlsZXMsIGJ5ID0gIkVVTklTYV8xIikNCg0KIyBkcGx5cjo6ZmlsdGVyIHJvd3MgYWJvdmUgdGhlIDIwdGggcGVyY2VudGlsZSBmb3IgYm90aCB2YXJpYWJsZXMgZm9yIGVhY2ggY2F0ZWdvcnkgb2YgRVVOSVNhXzENCmRwbHlyOjpmaWx0ZXJlZF9kYXRhMSA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBkcGx5cjo6ZmlsdGVyKA0KICAgIE5EVklfbWF4ID49IHBlcmNlbnRpbGVfMjBfTkRWSV9tYXggJiBORE1JX21pbiA+PSBwZXJjZW50aWxlXzIwX05ETUlfbWluDQogICAgKSAlPiUNCiAgZHBseXI6OmZpbHRlcighaXMubmEoTkRWSV9tYXgpICYgIWlzLm5hKE5ETUlfbWF4KSAmICFpcy5uYShORFdJX21heCkgJg0KICAgICAgICAgICAhaXMubmEoU0FWSV9tYXgpICYgIWlzLm5hKEVWSV9tYXgpICYgIWlzLm5hKE5EVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShORE1JX21pbikgJiAhaXMubmEoTkRXSV9taW4pICYgIWlzLm5hKFNBVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShFVklfbWluKSkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0KdHJhaW5faW5kaWNlczEgPC0gc2FtcGxlKDE6bnJvdyhkcGx5cjo6ZmlsdGVyZWRfZGF0YTEpLCAwLjcgKiBucm93KGRwbHlyOjpmaWx0ZXJlZF9kYXRhMSkpDQp0cmFpbl9kYXRhMSA8LSBkcGx5cjo6ZmlsdGVyZWRfZGF0YTFbdHJhaW5faW5kaWNlczEsIF0NCnRlc3RfZGF0YTEgPC0gZHBseXI6OmZpbHRlcmVkX2RhdGExWy10cmFpbl9pbmRpY2VzMSwgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBkcGx5cjo6ZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpkcGx5cjo6ZmlsdGVyZWRfZGF0YTEgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCkludmVzdGlnYXRlIHBhY2thZ2UgZ2dwYXJ0eSAoZS5nLiBhdXRvcGxvdCBmdW5jdGlvbiwgYW5kIG1vcmUpLg0KDQpUTy1ETzogDQpDaG9vc2UgdGhlIGh5cGVycGFyYW1ldGVyIG10cnkgYmFzZWQgb24gdGhlIHNxdWFyZSByb290IG9mIHRoZSBudW1iZXIgb2YgcHJlZGljdG9yIHZhcmlhYmxlcyAoSGFzdGllIGV0IGFsLiwgMjAwOSktDQoNCkhhc3RpZSwgVC4sIFRpYnNoaXJhbmksIFIuLCAmIEZyaWVkbWFuLCBKLiAoMjAwOSkuIFRoZSBlbGVtZW50cyBvZiBzdGF0aXN0aWNhbA0KbGVhcm5pbmc6IERhdGEgbWluaW5nLCBpbmZlcmVuY2UsIGFuZCBwcmVkaWN0aW9uLiBTcHJpbmdlciBTY2llbmNlICYNCkJ1c2luZXNzIE1lZGlhLg0KDQpNYXliZSBUT19ETzoNCldlIHZhcmlhdGVkIG50cmVlIGZyb20gNTAgdG8gODAwIGluIHN0ZXBzIG9mIDUwLCBsZWF2aW5nIG10cnkgY29uc3RhbnQgYXQgMi4gVGlzIHBhcmFtZXRlciB2YXJpYXRpb24gc2hvd2VkIHRoYXQgbnRyZWU9NTAwIHdhcyBvcHRpbWFsLCB3aGlsZSBoaWdoZXIgbnRyZWUgbGVkIHRvIG5vIGZ1cnRoZXIgbW9kZWwgaW1wcm92ZW1lbnQgKFN1cHBsZW1lbnRhcnkgRmlnLiBTMTApLiBTdWJzZXF1ZW50bHksIHRoZSBoeXBlcnBhcmFtZXRlciBtdHJ5IHdhcyB2YXJpZWQgZnJvbSAyIHRvIDggd2l0aCBjb25zdGFudCBudHJlZT01MDAuIEhlcmUsIG10cnk9MyBsZWQgdG8gdGhlIGJlc3QgcmVzdWx0cyBpbiBhbG1vc3QgYWxsIGNhc2VzIChTdXBwbGVtZW50YXJ5IEZpZy4gUzExKS4gQ29uc2VxdWVudGx5LCB3ZSBjaG9zZSBudHJlZT01MDAgYW5kIG10cnk9MyBmb3Igb3VyIG1haW4gYW5hbHlzaXMgYWNyb3NzIGFsbCBzdHVkeSBzaXRlcy4NCg0KYGBge3J9DQpyZl9jZm9yZXN0MSA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGExLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QxIDwtIHByZWRpY3QocmZfY2ZvcmVzdDEsIG5ld2RhdGEgPSB0ZXN0X2RhdGExLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0MSwgdGVzdF9kYXRhMSRFVU5JU2FfMSkNCmBgYA0KDQpTdXJyb2dhdGVUcmVlIC0tPiBkb2VzIG5vdCB3b3JrDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QxIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDEsIGNvbmRpdGlvbmFsID0gRikgDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZl9jb25kX2Nmb3Jlc3QxIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDEsIGNvbmRpdGlvbmFsID0gVCkNCiMgY29uZGl0aW9uYWwgPSBUIGFkanVzdHMgZm9yIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHByZWRpY3RvciB2YXJpYWJsZXMNCiMgVGFrZXMgbG9uZyENCnNhdmUodmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDEsIGZpbGUgPSAib2JqZWN0cy92YXJpbXBfcmZfY29uZF9jZm9yZXN0MS5SZGF0YSIpDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QxX2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0MSkNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDFfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNClRyZWUgVmlzdWFsaXphdGlvbg0KDQpgYGB7cn0NCiMgQ3JlYXRlIGEgc2luZ2xlIGNvbmRpdGlvbmFsIGluZmVyZW5jZSB0cmVlIHVzaW5nIGN0cmVlDQpzaW5nbGVfdHJlZTEgPC0gY3RyZWUoRVVOSVNhXzEgfiBORFZJX21heCArIE5EVklfbWluICsgTkRNSV9tYXggKyBORE1JX21pbiArDQogICAgICAgICAgICAgICAgICAgICAgIE5EV0lfbWF4ICsgTkRXSV9taW4gKyBFVklfbWF4ICsgRVZJX21pbiArIFNBVklfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgU0FWSV9taW4gKyBjYW5vcHlfaGVpZ2h0LA0KICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGExKQ0KDQojIFBsb3QgdGhlIHNpbmdsZSB0cmVlIHVzaW5nDQphdXRvcGxvdChzaW5nbGVfdHJlZTEpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMSRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jMSA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzENCmBgYA0KDQoNCiMjIEFsbCBHUFMgcG9pbnRzIHdpdGhpbiBJUSByYW5nZQ0KDQpkcGx5cjo6ZmlsdGVyIHRoZSBkYXRhIHRvIGdldCBvbmx5IEdQUy1wb2ludHMgd2l0aGluIElRIHJhbmdlIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbi4NCg0KYGBge3J9DQpJUV9yYW5nZXMgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzEpICU+JQ0KICBzdW1tYXJpemUoDQogICAgUTFfTkRWSV9tYXggPSBxdWFudGlsZShORFZJX21heCwgMC4yNSwgbmEucm0gPSBUKSwNCiAgICBRMV9ORE1JX21pbiA9IHF1YW50aWxlKE5ETUlfbWluLCAwLjI1LCBuYS5ybSA9IFQpLA0KICAgIFEzX05EVklfbWF4ID0gcXVhbnRpbGUoTkRWSV9tYXgsIDAuNzUsIG5hLnJtID0gVCksDQogICAgUTNfTkRNSV9taW4gPSBxdWFudGlsZShORE1JX21pbiwgMC43NSwgbmEucm0gPSBUKSwNCiAgICBJUVJfTkRWSV9tYXggPSBJUVIoTkRWSV9tYXgsIG5hLnJtID0gVFJVRSksDQogICAgSVFSX05ETUlfbWluID0gSVFSKE5ETUlfbWluLCBuYS5ybSA9IFRSVUUpDQogICAgKQ0KDQojIEpvaW4gdGhlIElRIHJhbmdlcyBiYWNrIHRvIHRoZSBvcmlnaW5hbCBkYXRhDQphbGxfR1BTX3ZhbGlkIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGxlZnRfam9pbihJUV9yYW5nZXMsIGJ5ID0gIkVVTklTYV8xIikNCg0KIyBGaWx0ZXIgcm93cyB3aXRoaW4gdGhlIElRUiByYW5nZSBmb3IgYm90aCB2YXJpYWJsZXMNCmRwbHlyOjpmaWx0ZXJlZF9kYXRhMiA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBkcGx5cjo6ZmlsdGVyKA0KICAgIChORFZJX21heCA+PSBRMV9ORFZJX21heCAmIE5EVklfbWF4IDw9IFEzX05EVklfbWF4KSAmDQogICAgKE5ETUlfbWluID49IFExX05ETUlfbWluICYgTkRNSV9taW4gPD0gUTNfTkRNSV9taW4pDQogICAgKSAlPiUNCiAgZHBseXI6OmZpbHRlcighaXMubmEoTkRWSV9tYXgpICYgIWlzLm5hKE5ETUlfbWF4KSAmICFpcy5uYShORFdJX21heCkgJg0KICAgICAgICAgICAhaXMubmEoU0FWSV9tYXgpICYgIWlzLm5hKEVWSV9tYXgpICYgIWlzLm5hKE5EVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShORE1JX21pbikgJiAhaXMubmEoTkRXSV9taW4pICYgIWlzLm5hKFNBVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShFVklfbWluKSkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0KdHJhaW5faW5kaWNlczIgPC0gc2FtcGxlKDE6bnJvdyhmaWx0ZXJlZF9kYXRhMiksIDAuNyAqIG5yb3coZmlsdGVyZWRfZGF0YTIpKQ0KdHJhaW5fZGF0YTIgPC0gZmlsdGVyZWRfZGF0YTJbdHJhaW5faW5kaWNlczIsIF0NCnRlc3RfZGF0YTIgPC0gZmlsdGVyZWRfZGF0YTJbLXRyYWluX2luZGljZXMyLCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGZpbHRlcmVkIGRhdGE6DQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTIgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KcmZfY2ZvcmVzdDIgPC0gcGFydHk6OmNmb3Jlc3QoRVVOSVNhXzEgfiBORFZJX21heCArIE5EVklfbWluICsgTkRNSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBORE1JX21pbiArIE5EV0lfbWF4ICsgTkRXSV9taW4gKyBFVklfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRVZJX21pbiArIFNBVklfbWF4ICsgU0FWSV9taW4gKyBjYW5vcHlfaGVpZ2h0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2xzID0gY2ZvcmVzdF9jb250cm9sKA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtdHJ5ID0gc3FydCgxMSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEZWZhdWx0IG10cnkgPSA1DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQmFnZ2luZzogbXRyeSA9IE5VTEwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBvciA9IG51bWJlciBvZiBpbnB1dCB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSA1MDApICMgRGVmYXVsdCwgdHJ5IGluY3JlYXNpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkgDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uc19yZl9jZm9yZXN0MiA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QyLCBuZXdkYXRhID0gdGVzdF9kYXRhMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT09CID0gVFJVRSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmZfY2ZvcmVzdDIsIHRlc3RfZGF0YTIkRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDIgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MiwgY29uZGl0aW9uYWwgPSBGKSANCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDIgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MiwgY29uZGl0aW9uYWwgPSBUKQ0KIyBjb25kaXRpb25hbCA9IFQgYWRqdXN0cyBmb3IgY29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlcw0KIyBUYWtlcyBsb25nIQ0Kc2F2ZSh2YXJpbXBfcmZfY29uZF9jZm9yZXN0MiwgZmlsZSA9ICJvYmplY3RzL3ZhcmltcF9yZl9jb25kX2Nmb3Jlc3QyLlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDJfZGYgPC0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IG5hbWVzKHZhcmltcF9yZl9jZm9yZXN0MiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbXBvcnRhbmNlID0gdmFyaW1wX3JmX2Nmb3Jlc3QyKQ0KZ2dwbG90KHZhcmltcF9yZl9jZm9yZXN0Ml9kZiwNCiAgICAgICBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBjb29yZF9mbGlwKCkgKyB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmxlIEltcG9ydGFuY2UiLCB4ID0gIlZhcmlhYmxlcyIsIHkgPSAiSW1wb3J0YW5jZSIpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMSRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jMiA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzINCmBgYA0KDQojIyBBbGwgR1BTIHBvaW50cyB3aXRoaW4gMS41ICogSVEgcmFuZ2UNCg0KRmlsdGVyIHRoZSBkYXRhIHRvIGdldCBvbmx5IEdQUy1wb2ludHMgd2l0aGluIDEuNSAqIElRIHJhbmdlIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbi4NCg0KYGBge3J9DQojIEZpbHRlciByb3dzIHdpdGhpbiB0aGUgMS41ICogSVFSIHJhbmdlIGZvciBib3RoIHZhcmlhYmxlcw0KZmlsdGVyZWRfZGF0YTMgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZmlsdGVyKA0KICAgIChORFZJX21heCA+PSAoUTFfTkRWSV9tYXggLSAxLjUgKiBJUVJfTkRWSV9tYXgpICYgTkRWSV9tYXggPD0gKFEzX05EVklfbWF4ICsgMS41ICogSVFSX05EVklfbWF4KSkgJg0KICAgICAgKE5ETUlfbWluID49IChRMV9ORE1JX21pbiAtIDEuNSAqIElRUl9ORE1JX21pbikgJiBORE1JX21pbiA8PSAoUTNfTkRNSV9taW4gKyAxLjUgKiBJUVJfTkRNSV9taW4pKQ0KICAgICkgJT4lDQogIGZpbHRlcighaXMubmEoTkRWSV9tYXgpICYgIWlzLm5hKE5ETUlfbWF4KSAmICFpcy5uYShORFdJX21heCkgJg0KICAgICAgICAgICAhaXMubmEoU0FWSV9tYXgpICYgIWlzLm5hKEVWSV9tYXgpICYgIWlzLm5hKE5EVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShORE1JX21pbikgJiAhaXMubmEoTkRXSV9taW4pICYgIWlzLm5hKFNBVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShFVklfbWluKSkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICBmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzMyA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGEzKSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhMykpDQp0cmFpbl9kYXRhMyA8LSBmaWx0ZXJlZF9kYXRhM1t0cmFpbl9pbmRpY2VzMywgXQ0KdGVzdF9kYXRhMyA8LSBmaWx0ZXJlZF9kYXRhM1stdHJhaW5faW5kaWNlczMsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMyAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0MyA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGEzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QzIDwtIHByZWRpY3QocmZfY2ZvcmVzdDMsIG5ld2RhdGEgPSB0ZXN0X2RhdGEzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0MywgdGVzdF9kYXRhMyRFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0MyA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QzLCBjb25kaXRpb25hbCA9IEYpIA0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmZfY29uZF9jZm9yZXN0MyA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QzLCBjb25kaXRpb25hbCA9IFQpDQojIGNvbmRpdGlvbmFsID0gVCBhZGp1c3RzIGZvciBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQojIFRha2VzIGxvbmchDQpzYXZlKHZhcmltcF9yZl9jb25kX2Nmb3Jlc3QzLCBmaWxlID0gIm9iamVjdHMvdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDMuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0M19kZiA8LSBkYXRhLmZyYW1lKFZhcmlhYmxlID0gbmFtZXModmFyaW1wX3JmX2Nmb3Jlc3QzKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEltcG9ydGFuY2UgPSB2YXJpbXBfcmZfY2ZvcmVzdDMpDQpnZ3Bsb3QodmFyaW1wX3JmX2Nmb3Jlc3QzX2RmLA0KICAgICAgIGFlcyh4ID0gcmVvcmRlcihWYXJpYWJsZSwgSW1wb3J0YW5jZSksIHkgPSBJbXBvcnRhbmNlKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGNvb3JkX2ZsaXAoKSArIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSIsIHggPSAiVmFyaWFibGVzIiwgeSA9ICJJbXBvcnRhbmNlIikNCmBgYA0KDQpST0MgY3VydmVzOg0KDQpgYGB7cn0NCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzIGZvciBlYWNoIGNsYXNzDQpwcm9iYWJpbGl0aWVzIDwtIHByZWRpY3QocmZfY2ZvcmVzdDEsIG5ld2RhdGEgPSB0ZXN0X2RhdGExLCB0eXBlID0gInByb2IiKQ0KDQojIFN0ZXAgMTogQ29udmVydCBsaXN0IG9mIG1hdHJpY2VzIHRvIGEgcHJvcGVyIGRhdGEgZnJhbWUNCnByb2JfbWF0cml4IDwtIHQoc2FwcGx5KHByb2JhYmlsaXRpZXMsIGFzLnZlY3RvcikpDQpjb2xuYW1lcyhwcm9iX21hdHJpeCkgPC0gYygiUSIsICJSIiwgIlMiLCAiVCIpICAjIEFkanVzdCBpZiBuZWVkZWQNCnByb2JfZGYgPC0gYXMuZGF0YS5mcmFtZShwcm9iX21hdHJpeCkNCg0KIyBTdGVwIDI6IFByZXBhcmUgYWN0dWFsIGNsYXNzIGxhYmVscw0KYWN0dWFsIDwtIGZhY3Rvcih0ZXN0X2RhdGExJEVVTklTYV8xLCBsZXZlbHMgPSBjKCJRIiwgIlIiLCAiUyIsICJUIikpDQpjbGFzc2VzIDwtIGxldmVscyhhY3R1YWwpDQoNCiMgU3RlcCAzOiBCaW5hcml6ZSBhY3R1YWwgbGFiZWxzDQphY3R1YWxfYmluIDwtIG1vZGVsLm1hdHJpeCh+IGFjdHVhbCAtIDEpDQpjb2xuYW1lcyhhY3R1YWxfYmluKSA8LSBnc3ViKCJhY3R1YWwiLCAiIiwgY29sbmFtZXMoYWN0dWFsX2JpbikpDQoNCiMgU3RlcCA0OiBDb21wdXRlIFJPQyBkYXRhIGZvciBlYWNoIGNsYXNzIHdpdGggQVVDIGluIGxhYmVsDQpyb2NfZGF0YSA8LSBsYXBwbHkoY2xhc3NlcywgZnVuY3Rpb24oY2xhc3MpIHsNCiAgcm9jX29iaiA8LSByb2MoYWN0dWFsX2JpblssIGNsYXNzXSwgcHJvYl9kZltbY2xhc3NdXSkNCiAgYXVjX3ZhbCA8LSByb3VuZChhdWMocm9jX29iaiksIDMpDQogIGRhdGEuZnJhbWUoDQogICAgRlBSID0gcmV2KHJvY19vYmokc3BlY2lmaWNpdGllcyksDQogICAgVFBSID0gcmV2KHJvY19vYmokc2Vuc2l0aXZpdGllcyksDQogICAgQ2xhc3MgPSBwYXN0ZTAoY2xhc3MsICIgKEFVQyA9ICIsIGF1Y192YWwsICIpIikNCiAgKQ0KfSkgJT4lIGJpbmRfcm93cygpDQoNCiMgU3RlcCA1OiBQbG90IFJPQyBjdXJ2ZXMgd2l0aCBnZ3Bsb3QyDQpyb2MzIDwtIGdncGxvdChyb2NfZGF0YSwgYWVzKHggPSBGUFIsIHkgPSBUUFIsIGNvbG9yID0gQ2xhc3MpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yKSArDQogIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyYXkiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTXVsdGljbGFzcyBST0MgQ3VydmVzIHdpdGggQVVDIiwNCiAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLA0KICAgIHkgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICBjb2xvciA9ICJDbGFzcyAoQVVDKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0Kcm9jMw0KYGBgDQoNCiMjIEFsbCBHUFMgcG9pbnRzIHdpdGhpbiBtZWFuICsvLSBTRA0KDQpGaWx0ZXIgdGhlIGRhdGEgdG8gZ2V0IG9ubHkgR1BTLXBvaW50cyB3aXRoaW4gbWVhbiArLy0gU0Qgb2YgTkRWSV9tYXggYW5kIE5ETUlfbWluLg0KDQpgYGB7cn0NCm1lYW5fc2QgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzEpICU+JQ0KICBzdW1tYXJpemUoDQogICAgbWVhbl9ORFZJX21heCA9IG1lYW4oYWxsX0dQU192YWxpZCRORFZJX21heCwgbmEucm0gPSBUKSwNCiAgICBtZWFuX05ETUlfbWluID0gbWVhbihhbGxfR1BTX3ZhbGlkJE5ETUlfbWluLCBuYS5ybSA9IFQpLA0KICAgIHNkX05EVklfbWF4ID0gc2QoYWxsX0dQU192YWxpZCRORFZJX21heCwgbmEucm0gPSBUKSwNCiAgICBzZF9ORE1JX21pbiA9IHNkKGFsbF9HUFNfdmFsaWQkTkRNSV9taW4sIG5hLnJtID0gVCkNCiAgICApDQoNCiMgSm9pbiB0aGUgSVEgcmFuZ2VzIGJhY2sgdG8gdGhlIG9yaWdpbmFsIGRhdGENCmFsbF9HUFNfdmFsaWQgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgbGVmdF9qb2luKG1lYW5fc2QsIGJ5ID0gIkVVTklTYV8xIikNCg0KIyBGaWx0ZXIgcm93cyB3aXRoaW4gdGhlIHNwZWNpZmllZCByYW5nZSBmb3IgYm90aCB2YXJpYWJsZXMNCmZpbHRlcmVkX2RhdGE0IDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGZpbHRlcigNCiAgICAoTkRWSV9tYXggPj0gKG1lYW5fTkRWSV9tYXggLSBzZF9ORFZJX21heCkgJiBORFZJX21heCA8PSAobWVhbl9ORFZJX21heCArIHNkX05EVklfbWF4KSkgJg0KICAgICAgKE5ETUlfbWluID49IChtZWFuX05ETUlfbWluIC0gc2RfTkRNSV9taW4pICYgTkRNSV9taW4gPD0gKG1lYW5fTkRNSV9taW4gKyBzZF9ORE1JX21pbikpDQogICAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShORFZJX21heCkgJiAhaXMubmEoTkRNSV9tYXgpICYgIWlzLm5hKE5EV0lfbWF4KSAmDQogICAgICAgICAgICFpcy5uYShTQVZJX21heCkgJiAhaXMubmEoRVZJX21heCkgJiAhaXMubmEoTkRWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKE5ETUlfbWluKSAmICFpcy5uYShORFdJX21pbikgJiAhaXMubmEoU0FWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKEVWSV9taW4pKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGZpbHRlcihFVklfbWF4IDw9IDEgJiBFVklfbWluID49IC0xKQ0KYGBgDQoNClNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnRyYWluX2luZGljZXM0IDwtIHNhbXBsZSgxOm5yb3coZmlsdGVyZWRfZGF0YTQpLCAwLjcgKiBucm93KGZpbHRlcmVkX2RhdGE0KSkNCnRyYWluX2RhdGE0IDwtIGZpbHRlcmVkX2RhdGE0W3RyYWluX2luZGljZXM0LCBdDQp0ZXN0X2RhdGE0IDwtIGZpbHRlcmVkX2RhdGE0Wy10cmFpbl9pbmRpY2VzNCwgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGE0ICU+JSBjb3VudChFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnJmX2Nmb3Jlc3Q0IDwtIHBhcnR5OjpjZm9yZXN0KEVVTklTYV8xIH4gTkRWSV9tYXggKyBORFZJX21pbiArIE5ETUlfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkRNSV9taW4gKyBORFdJX21heCArIE5EV0lfbWluICsgRVZJX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVWSV9taW4gKyBTQVZJX21heCArIFNBVklfbWluICsgY2Fub3B5X2hlaWdodCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YTQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9scyA9IGNmb3Jlc3RfY29udHJvbCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyeSA9IDMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbXRyeSA9IHNxcnQoMTEpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApIA0KYGBgDQoNCmBgYHtyfQ0KcHJlZGljdGlvbnNfcmZfY2ZvcmVzdDQgPC0gcHJlZGljdChyZl9jZm9yZXN0NCwgbmV3ZGF0YSA9IHRlc3RfZGF0YTQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9PQiA9IFRSVUUsIHR5cGUgPSAicmVzcG9uc2UiKQ0KYGBgDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zX3JmX2Nmb3Jlc3Q0LCB0ZXN0X2RhdGE0JEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3Q0IDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDQsIGNvbmRpdGlvbmFsID0gRikgDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZl9jb25kX2Nmb3Jlc3Q0IDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDQsIGNvbmRpdGlvbmFsID0gVCkNCiMgY29uZGl0aW9uYWwgPSBUIGFkanVzdHMgZm9yIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHByZWRpY3RvciB2YXJpYWJsZXMNCiMgVGFrZXMgbG9uZyENCnNhdmUodmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDQsIGZpbGUgPSAib2JqZWN0cy92YXJpbXBfcmZfY29uZF9jZm9yZXN0NC5SZGF0YSIpDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3Q0X2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0NCkNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDRfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNClJPQyBjdXJ2ZXM6DQoNCmBgYHtyfQ0KIyBQcmVkaWN0IHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggY2xhc3MNCnByb2JhYmlsaXRpZXMgPC0gcHJlZGljdChyZl9jZm9yZXN0MSwgbmV3ZGF0YSA9IHRlc3RfZGF0YTEsIHR5cGUgPSAicHJvYiIpDQoNCiMgU3RlcCAxOiBDb252ZXJ0IGxpc3Qgb2YgbWF0cmljZXMgdG8gYSBwcm9wZXIgZGF0YSBmcmFtZQ0KcHJvYl9tYXRyaXggPC0gdChzYXBwbHkocHJvYmFiaWxpdGllcywgYXMudmVjdG9yKSkNCmNvbG5hbWVzKHByb2JfbWF0cml4KSA8LSBjKCJRIiwgIlIiLCAiUyIsICJUIikgICMgQWRqdXN0IGlmIG5lZWRlZA0KcHJvYl9kZiA8LSBhcy5kYXRhLmZyYW1lKHByb2JfbWF0cml4KQ0KDQojIFN0ZXAgMjogUHJlcGFyZSBhY3R1YWwgY2xhc3MgbGFiZWxzDQphY3R1YWwgPC0gZmFjdG9yKHRlc3RfZGF0YTEkRVVOSVNhXzEsIGxldmVscyA9IGMoIlEiLCAiUiIsICJTIiwgIlQiKSkNCmNsYXNzZXMgPC0gbGV2ZWxzKGFjdHVhbCkNCg0KIyBTdGVwIDM6IEJpbmFyaXplIGFjdHVhbCBsYWJlbHMNCmFjdHVhbF9iaW4gPC0gbW9kZWwubWF0cml4KH4gYWN0dWFsIC0gMSkNCmNvbG5hbWVzKGFjdHVhbF9iaW4pIDwtIGdzdWIoImFjdHVhbCIsICIiLCBjb2xuYW1lcyhhY3R1YWxfYmluKSkNCg0KIyBTdGVwIDQ6IENvbXB1dGUgUk9DIGRhdGEgZm9yIGVhY2ggY2xhc3Mgd2l0aCBBVUMgaW4gbGFiZWwNCnJvY19kYXRhIDwtIGxhcHBseShjbGFzc2VzLCBmdW5jdGlvbihjbGFzcykgew0KICByb2Nfb2JqIDwtIHJvYyhhY3R1YWxfYmluWywgY2xhc3NdLCBwcm9iX2RmW1tjbGFzc11dKQ0KICBhdWNfdmFsIDwtIHJvdW5kKGF1Yyhyb2Nfb2JqKSwgMykNCiAgZGF0YS5mcmFtZSgNCiAgICBGUFIgPSByZXYocm9jX29iaiRzcGVjaWZpY2l0aWVzKSwNCiAgICBUUFIgPSByZXYocm9jX29iaiRzZW5zaXRpdml0aWVzKSwNCiAgICBDbGFzcyA9IHBhc3RlMChjbGFzcywgIiAoQVVDID0gIiwgYXVjX3ZhbCwgIikiKQ0KICApDQp9KSAlPiUgYmluZF9yb3dzKCkNCg0KIyBTdGVwIDU6IFBsb3QgUk9DIGN1cnZlcyB3aXRoIGdncGxvdDINCnJvYzQgPC0gZ2dwbG90KHJvY19kYXRhLCBhZXMoeCA9IEZQUiwgeSA9IFRQUiwgY29sb3IgPSBDbGFzcykpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgZ2VvbV9hYmxpbmUobGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNdWx0aWNsYXNzIFJPQyBDdXJ2ZXMgd2l0aCBBVUMiLA0KICAgIHggPSAiRmFsc2UgUG9zaXRpdmUgUmF0ZSIsDQogICAgeSA9ICJUcnVlIFBvc2l0aXZlIFJhdGUiLA0KICAgIGNvbG9yID0gIkNsYXNzIChBVUMpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQpyb2M0DQpgYGANCg0KIyMgQWxsIEdQUyBwb2ludHMgYWJvdmUgcDIwIGFuZCBiZWxvdyBwODANCg0KRmlsdGVyIHRoZSBkYXRhIHRvIGdldCBvbmx5IEdQUy1wb2ludHMgYWJvdmUgcDIwIGFuZCBiZWxvdyBwODAgb2YgTkRWSV9tYXggYW5kIE5ETUlfbWluLg0KDQpgYGB7cn0NCiMgRmlsdGVyIHJvd3MgYWJvdmUgdGhlIDIwdGggcGVyY2VudGlsZSBhbmQgYmVsb3cgdGhlIDgwdGggcGVyY2VudGlsZSBmb3IgYm90aCB2YXJpYWJsZXMNCmZpbHRlcmVkX2RhdGE1IDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGZpbHRlcigNCiAgICAoTkRWSV9tYXggPj0gcGVyY2VudGlsZV8yMF9ORFZJX21heCAmIE5EVklfbWF4IDw9IHBlcmNlbnRpbGVfODBfTkRWSV9tYXgpICYNCiAgICAoTkRNSV9taW4gPj0gcGVyY2VudGlsZV8yMF9ORE1JX21pbiAmIE5ETUlfbWluIDw9IHBlcmNlbnRpbGVfODBfTkRNSV9taW4pDQogICAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShORFZJX21heCkgJiAhaXMubmEoTkRNSV9tYXgpICYgIWlzLm5hKE5EV0lfbWF4KSAmDQogICAgICAgICAgICFpcy5uYShTQVZJX21heCkgJiAhaXMubmEoRVZJX21heCkgJiAhaXMubmEoTkRWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKE5ETUlfbWluKSAmICFpcy5uYShORFdJX21pbikgJiAhaXMubmEoU0FWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKEVWSV9taW4pKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGZpbHRlcihFVklfbWF4IDw9IDEgJiBFVklfbWluID49IC0xKQ0KYGBgDQoNClNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnRyYWluX2luZGljZXM1IDwtIHNhbXBsZSgxOm5yb3coZmlsdGVyZWRfZGF0YTUpLCAwLjcgKiBucm93KGZpbHRlcmVkX2RhdGE1KSkNCnRyYWluX2RhdGE1IDwtIGZpbHRlcmVkX2RhdGE1W3RyYWluX2luZGljZXM1LCBdDQp0ZXN0X2RhdGE1IDwtIGZpbHRlcmVkX2RhdGE1Wy10cmFpbl9pbmRpY2VzNSwgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGE1ICU+JSBjb3VudChFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnJmX2Nmb3Jlc3Q1IDwtIHBhcnR5OjpjZm9yZXN0KEVVTklTYV8xIH4gTkRWSV9tYXggKyBORFZJX21pbiArIE5ETUlfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkRNSV9taW4gKyBORFdJX21heCArIE5EV0lfbWluICsgRVZJX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVWSV9taW4gKyBTQVZJX21heCArIFNBVklfbWluICsgY2Fub3B5X2hlaWdodCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YTUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9scyA9IGNmb3Jlc3RfY29udHJvbCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyeSA9IDMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbXRyeSA9IHNxcnQoMTEpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApIA0KYGBgDQoNCmBgYHtyfQ0KcHJlZGljdGlvbnNfcmZfY2ZvcmVzdDUgPC0gcHJlZGljdChyZl9jZm9yZXN0NSwgbmV3ZGF0YSA9IHRlc3RfZGF0YTUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9PQiA9IFRSVUUsIHR5cGUgPSAicmVzcG9uc2UiKQ0KYGBgDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zX3JmX2Nmb3Jlc3Q1LCB0ZXN0X2RhdGE1JEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3Q1IDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDUsIGNvbmRpdGlvbmFsID0gRikgDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZl9jb25kX2Nmb3Jlc3Q1IDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDUsIGNvbmRpdGlvbmFsID0gVCkNCiMgY29uZGl0aW9uYWwgPSBUIGFkanVzdHMgZm9yIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHByZWRpY3RvciB2YXJpYWJsZXMNCiMgVGFrZXMgbG9uZyENCnNhdmUodmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDUsIGZpbGUgPSAib2JqZWN0cy92YXJpbXBfcmZfY29uZF9jZm9yZXN0NS5SZGF0YSIpDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3Q1X2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0NSkNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDVfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNClJPQyBjdXJ2ZXM6DQoNCmBgYHtyfQ0KIyBQcmVkaWN0IHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggY2xhc3MNCnByb2JhYmlsaXRpZXMgPC0gcHJlZGljdChyZl9jZm9yZXN0MSwgbmV3ZGF0YSA9IHRlc3RfZGF0YTEsIHR5cGUgPSAicHJvYiIpDQoNCiMgU3RlcCAxOiBDb252ZXJ0IGxpc3Qgb2YgbWF0cmljZXMgdG8gYSBwcm9wZXIgZGF0YSBmcmFtZQ0KcHJvYl9tYXRyaXggPC0gdChzYXBwbHkocHJvYmFiaWxpdGllcywgYXMudmVjdG9yKSkNCmNvbG5hbWVzKHByb2JfbWF0cml4KSA8LSBjKCJRIiwgIlIiLCAiUyIsICJUIikgICMgQWRqdXN0IGlmIG5lZWRlZA0KcHJvYl9kZiA8LSBhcy5kYXRhLmZyYW1lKHByb2JfbWF0cml4KQ0KDQojIFN0ZXAgMjogUHJlcGFyZSBhY3R1YWwgY2xhc3MgbGFiZWxzDQphY3R1YWwgPC0gZmFjdG9yKHRlc3RfZGF0YTEkRVVOSVNhXzEsIGxldmVscyA9IGMoIlEiLCAiUiIsICJTIiwgIlQiKSkNCmNsYXNzZXMgPC0gbGV2ZWxzKGFjdHVhbCkNCg0KIyBTdGVwIDM6IEJpbmFyaXplIGFjdHVhbCBsYWJlbHMNCmFjdHVhbF9iaW4gPC0gbW9kZWwubWF0cml4KH4gYWN0dWFsIC0gMSkNCmNvbG5hbWVzKGFjdHVhbF9iaW4pIDwtIGdzdWIoImFjdHVhbCIsICIiLCBjb2xuYW1lcyhhY3R1YWxfYmluKSkNCg0KIyBTdGVwIDQ6IENvbXB1dGUgUk9DIGRhdGEgZm9yIGVhY2ggY2xhc3Mgd2l0aCBBVUMgaW4gbGFiZWwNCnJvY19kYXRhIDwtIGxhcHBseShjbGFzc2VzLCBmdW5jdGlvbihjbGFzcykgew0KICByb2Nfb2JqIDwtIHJvYyhhY3R1YWxfYmluWywgY2xhc3NdLCBwcm9iX2RmW1tjbGFzc11dKQ0KICBhdWNfdmFsIDwtIHJvdW5kKGF1Yyhyb2Nfb2JqKSwgMykNCiAgZGF0YS5mcmFtZSgNCiAgICBGUFIgPSByZXYocm9jX29iaiRzcGVjaWZpY2l0aWVzKSwNCiAgICBUUFIgPSByZXYocm9jX29iaiRzZW5zaXRpdml0aWVzKSwNCiAgICBDbGFzcyA9IHBhc3RlMChjbGFzcywgIiAoQVVDID0gIiwgYXVjX3ZhbCwgIikiKQ0KICApDQp9KSAlPiUgYmluZF9yb3dzKCkNCg0KIyBTdGVwIDU6IFBsb3QgUk9DIGN1cnZlcyB3aXRoIGdncGxvdDINCnJvYzUgPC0gZ2dwbG90KHJvY19kYXRhLCBhZXMoeCA9IEZQUiwgeSA9IFRQUiwgY29sb3IgPSBDbGFzcykpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgZ2VvbV9hYmxpbmUobGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNdWx0aWNsYXNzIFJPQyBDdXJ2ZXMgd2l0aCBBVUMiLA0KICAgIHggPSAiRmFsc2UgUG9zaXRpdmUgUmF0ZSIsDQogICAgeSA9ICJUcnVlIFBvc2l0aXZlIFJhdGUiLA0KICAgIGNvbG9yID0gIkNsYXNzIChBVUMpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQpyb2M1DQpgYGANCg0KIyBIRVJFOiBDb21wYXJlIFJGIDEtNQ0KDQojIENvcmRpbGxlcmEgZGF0YQ0KDQpgYGB7cn0NCkFscGluZUdyYXNzbGFuZHNfaW5kaWNlcyA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9BbHBpbmVHcmFzc2xhbmRzL0FscGluZUdyYXNzbGFuZF9TZW50aW5lbF9QbG90X0FsbHllYXJfQWxsbWV0cmljcy5jc3YiKQ0KQWxwaW5lR3Jhc3NsYW5kc19waGVuIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL0FscGluZUdyYXNzbGFuZHMvQWxwaW5lR3Jhc3NsYW5kc19QaGVub2xvZ3lfU09TX0VPU19QZWFrX05EVklfQW1wbGl0dWRlLmNzdiIpDQpBbHBpbmVHcmFzc2xhbmRzX0NIIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL0FscGluZUdyYXNzbGFuZHMvQWxwaW5lR3Jhc3NsYW5kc19DYW5vcHlIZWlnaHRfMW0uY3N2IikNClZlZ2V0YXRpb25UeXBlc19pbmRpY2VzIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL1ZlZ2V0YXRpb25UeXBlcy9WZWdldGF0aW9uVHlwZXNfU2VudGluZWxfUGxvdF9BbGxZZWFyX0FsbG1ldHJpY3MuY3N2IikNClZlZ2V0YXRpb25UeXBlc19waGVuIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL1ZlZ2V0YXRpb25UeXBlcy9WZWdldGF0aW9uVHlwZXNfUGhlbm9sb2d5X1NPU19FT1NfUGVha19ORFZJX0FtcGxpdHVkZS5jc3YiKQ0KVmVnZXRhdGlvblR5cGVzX0NIIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9Db3JkaWxsZXJhL1ZlZ2V0YXRpb25UeXBlcy9WZWdldGF0aW9uVHlwZXNfQ2Fub3B5SGVpZ2h0XzFtLmNzdiIpDQpgYGANCg0KYGBge3J9DQpBbHBpbmVHcmFzc2xhbmRzIDwtIEFscGluZUdyYXNzbGFuZHNfaW5kaWNlcyAlPiUNCiAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8sIC1Mb2NhbGlkYWQpICU+JQ0KICByZW5hbWUoSMOhYml0YXQgPSAiSO+/vWJpdGF0IikgJT4lIA0KICBmdWxsX2pvaW4oQWxwaW5lR3Jhc3NsYW5kc19waGVuICAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8sIC1Mb2NhbGlkYWQpICU+JQ0KICAgICAgICAgICAgICByZW5hbWUoSMOhYml0YXQgPSAiSO+/vWJpdGF0IikpICU+JQ0KICBmdWxsX2pvaW4oQWxwaW5lR3Jhc3NsYW5kc19DSCAgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvLCAtTG9jYWxpZGFkKSkgJT4lDQogIHNlbGVjdCgtRGF0ZV9feWVhciwgLSBgUHJlY2lzae+/vW5gKSAlPiUNCiAgbXV0YXRlKERBVEUgPSB5bWQoREFURSkpICU+JQ0KICByZW5hbWUoSUQgPSAiUmVsZXZlX251bSIpICU+JQ0KICBtdXRhdGUoSUQgPSBhcy5jaGFyYWN0ZXIoSUQpKSAlPiUNCiAgbXV0YXRlKGxheWVyID0gIkFscGluZUdyYXNzbGFuZHMiKQ0KYGBgDQoNCmBgYHtyfQ0KVmVnZXRhdGlvblR5cGVzIDwtIFZlZ2V0YXRpb25UeXBlc19pbmRpY2VzICU+JQ0KICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbykgJT4lDQogIGZ1bGxfam9pbihWZWdldGF0aW9uVHlwZXNfcGhlbiAgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvKSkgJT4lDQogIGZ1bGxfam9pbihWZWdldGF0aW9uVHlwZXNfQ0ggICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbykpICU+JQ0KICByZW5hbWUoSMOhYml0YXQgPSAiVFlQRSIpICU+JQ0KICBtdXRhdGUobGF5ZXIgPSAiVmVnZXRhdGlvblR5cGVzIikNCmBgYA0KDQpNZXJnZSBib3RoIGRhdGFzZXRzOg0KDQpgYGB7cn0NCmNvcmRpbGxlcmEgPC0gYmluZF9yb3dzKA0KICBBbHBpbmVHcmFzc2xhbmRzICU+JSBzZWxlY3QoREFURSwgSUQsIHN0YXJ0c193aXRoKCJORE1JIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFydHNfd2l0aCgiTkRWSSIpLCBIw6FiaXRhdCwgIkVPU19ET1kiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBlYWtfRE9ZIiwgIlNPU19ET1kiLCAiU2Vhc29uX0xlbmd0aCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY2Fub3B5X2hlaWdodCIsICJsYXllciIpLA0KICBWZWdldGF0aW9uVHlwZXMgJT4lIHNlbGVjdChEQVRFLCBJRCwgc3RhcnRzX3dpdGgoIk5ETUkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0c193aXRoKCJORFZJIiksIEjDoWJpdGF0LCAiRU9TX0RPWSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUGVha19ET1kiLCAiU09TX0RPWSIsICJTZWFzb25fTGVuZ3RoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjYW5vcHlfaGVpZ2h0IiwgImxheWVyIikNCiAgKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gY2FzZV93aGVuKA0KICAgIEjDoWJpdGF0ID0gc3RyX2RldGVjdChIw6FiaXRhdCwgIlBhc3RpemFsfENlcnZ1bmFsfGdyYXNzbGFuZHxtZWFkb3ciKSB+ICJSIiwNCiAgICBIw6FiaXRhdCA9IHN0cl9kZXRlY3QoSMOhYml0YXQsICJmb3Jlc3QiKSB+ICJUIiwNCiAgICBIw6FiaXRhdCA9IHN0cl9kZXRlY3QoSMOhYml0YXQsICJTY3J1YnxzY3J1YnxTaHJ1YmxhbmR8c2hydWJsYW5kfHNocnVifEhlYXRobGFuZCIpIH4gIlMiLA0KICAgIEjDoWJpdGF0ID0gc3RyX2RldGVjdChIw6FiaXRhdCwgIlN1ZWxvfFNjcmVlfHNjcmVlfGNsaWZmIikgfiAiVSIsDQogICAgSMOhYml0YXQgPSBpcy5uYShIw6FiaXRhdCkgfiAiUiIsDQogICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8pLA0KICAgIEVVTklTYV8xX2Rlc2NyID0gY2FzZV93aGVuKA0KICAgICAgRVVOSVNhXzEgPT0gIlIiIH4gIkdyYXNzbGFuZHMiLA0KICAgICAgRVVOSVNhXzEgPT0gIlQiIH4gIkZvcmVzdHMgYW5kIG90aGVyIHdvb2RlZCBsYW5kIiwNCiAgICAgIEVVTklTYV8xID09ICJTIiB+ICJIZWF0aGxhbmRzLCBzY3J1YiBhbmQgdHVuZHJhIiwNCiAgICAgIEVVTklTYV8xID09ICJVIiB+ICJJbmxhbmQgaGFiaXRhdHMgd2l0aCBubyBvciBsaXR0bGUgc29pbCIpDQogICAgKQ0KYGBgDQoNCiMjIE5EVkksIE5ETUkNCg0KYGBge3J9DQpkaXN0cl9wbG90KGNvcmRpbGxlcmEsDQogICAgICAgICAgIGMoIk5EVklfbWF4IiwgIk5EVklfcDkwIiwgIk5EVklfbWluIiwgIk5EVklfcDEwIiksIA0KICAgICAgICAgICBjKCJORFZJIG1heCIsICJORFZJIHA5MCIsICJORFZJIG1pbiIsICJORFZJIHAxMCIpKQ0KZGlzdHJfcGxvdChjb3JkaWxsZXJhLA0KICAgICAgICAgICBjKCJORE1JX21heCIsICJORE1JX3A5MCIsICJORE1JX21pbiIsICJORE1JX3AxMCIpLCANCiAgICAgICAgICAgYygiTkRNSSBtYXgiLCAiTkRNSSBwOTAiLCAiTkRNSSBtaW4iLCAiTkRNSSBwMTAiKSkNCmBgYA0KDQojIFNlc3Npb24gaW5mbw0KDQpgYGB7cn0NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQo=